mirror of
https://github.com/rowyio/rowy.git
synced 2026-02-23 19:50:01 +01:00
ft build streamer: add snack log window
This commit is contained in:
@@ -17,7 +17,7 @@ import { useAppContext } from "contexts/AppContext";
|
||||
|
||||
import CodeEditor from "../editors/CodeEditor";
|
||||
|
||||
export default function SparksEditor() {
|
||||
export default function SparksEditor({ requestSnackLog }) {
|
||||
const snack = useSnackContext();
|
||||
const { tableState, tableActions } = useFiretableContext();
|
||||
const appContext = useAppContext();
|
||||
@@ -64,6 +64,7 @@ export default function SparksEditor() {
|
||||
const userTokenInfo = await appContext?.currentUser?.getIdTokenResult();
|
||||
const userToken = userTokenInfo?.token;
|
||||
try {
|
||||
requestSnackLog(Date.now());
|
||||
const response = await fetch(ftBuildUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -18,12 +18,17 @@ import {
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
IconButton,
|
||||
} from "@material-ui/core";
|
||||
import Modal from "components/Modal";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import LogsIcon from "@material-ui/icons/QueryBuilder";
|
||||
import SuccessIcon from "@material-ui/icons/CheckCircle";
|
||||
import FailIcon from "@material-ui/icons/Cancel";
|
||||
import ExpandIcon from "@material-ui/icons/ExpandLess";
|
||||
import CollapseIcon from "@material-ui/icons/ExpandMore";
|
||||
import OpenIcon from "@material-ui/icons/OpenInNew";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import TableHeaderButton from "./TableHeaderButton";
|
||||
import { LOG_FONT, LOG_TEXT } from "Themes";
|
||||
import Ansi from "ansi-to-react";
|
||||
@@ -38,6 +43,12 @@ function a11yProps(index) {
|
||||
};
|
||||
}
|
||||
|
||||
const isTargetInsideBox = (target, box) => {
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const boxRect = box.getBoundingClientRect();
|
||||
return targetRect.y < boxRect.y + boxRect.height;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
@@ -60,7 +71,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
backgroundColor: "#1E1E1E",
|
||||
},
|
||||
logPanelProgress: {
|
||||
marginLeft: "3em",
|
||||
marginLeft: "2em",
|
||||
marginTop: "1em",
|
||||
},
|
||||
logEntryWrapper: {
|
||||
@@ -69,13 +80,13 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
logNumber: {
|
||||
float: "left",
|
||||
width: "3em",
|
||||
width: "2em",
|
||||
textAlign: "right",
|
||||
paddingRight: "1em",
|
||||
},
|
||||
logEntry: {
|
||||
lineBreak: "anywhere",
|
||||
paddingLeft: "3em",
|
||||
paddingLeft: "2em",
|
||||
whiteSpace: "break-spaces",
|
||||
userSelect: "text",
|
||||
},
|
||||
@@ -96,6 +107,22 @@ const useStyles = makeStyles((theme) => ({
|
||||
fontFamily: LOG_FONT,
|
||||
},
|
||||
},
|
||||
|
||||
snackLog: {
|
||||
position: "absolute",
|
||||
left: 40,
|
||||
bottom: 40,
|
||||
backgroundColor: "#282829",
|
||||
width: "min(40vw, 640px)",
|
||||
padding: theme.spacing(1, 2, 2, 2),
|
||||
borderRadius: 4,
|
||||
zIndex: 1,
|
||||
height: 300,
|
||||
transition: "height 300ms ease-out",
|
||||
},
|
||||
snackLogExpanded: {
|
||||
height: "calc(100% - 300px)",
|
||||
},
|
||||
}));
|
||||
|
||||
LogPanel.propTypes = {
|
||||
@@ -161,7 +188,7 @@ function LogPanel(props) {
|
||||
console.log("live streaming:", liveStreamTargetVisible);
|
||||
setLiveStreaming(liveStreamTargetVisible);
|
||||
}
|
||||
}, 100);
|
||||
}, 500);
|
||||
|
||||
const scrollToLive = () => {
|
||||
const liveStreamTarget = document.querySelector("#live-stream-target");
|
||||
@@ -170,12 +197,6 @@ function LogPanel(props) {
|
||||
});
|
||||
};
|
||||
|
||||
const isTargetInsideBox = (target, box) => {
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const boxRect = box.getBoundingClientRect();
|
||||
return targetRect.y < boxRect.y + boxRect.height;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (liveStreaming && isActive && status === "BUILDING") {
|
||||
if (!liveStreamingRef.current) {
|
||||
@@ -230,13 +251,141 @@ function LogPanel(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableLogs() {
|
||||
function SnackLog({ log, onClose, onOpenPanel }) {
|
||||
const logs = log?.fullLog;
|
||||
const status = log?.status;
|
||||
const classes = useStyles();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [liveStreaming, setLiveStreaming, liveStreamingStateRef] = useStateRef(
|
||||
true
|
||||
);
|
||||
const liveStreamingRef = useRef<any>();
|
||||
|
||||
const handleScroll = _throttle(() => {
|
||||
const target = document.querySelector("#live-stream-target-snack");
|
||||
const scrollBox = document.querySelector("#live-stream-scroll-box-snack");
|
||||
const liveStreamTargetVisible =
|
||||
target && scrollBox && isTargetInsideBox(target, scrollBox);
|
||||
if (liveStreamTargetVisible !== liveStreamingStateRef.current) {
|
||||
console.log("live streaming:", liveStreamTargetVisible);
|
||||
setLiveStreaming(liveStreamTargetVisible);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
const scrollToLive = () => {
|
||||
const liveStreamTarget = document.querySelector(
|
||||
"#live-stream-target-snack"
|
||||
);
|
||||
liveStreamTarget?.scrollIntoView?.();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (liveStreaming && status === "BUILDING") {
|
||||
if (!liveStreamingRef.current) {
|
||||
scrollToLive();
|
||||
} else {
|
||||
setTimeout(scrollToLive, 500);
|
||||
}
|
||||
}
|
||||
}, [log]);
|
||||
|
||||
useEffect(() => {
|
||||
const liveStreamScrollBox = document.querySelector(
|
||||
"#live-stream-scroll-box-snack"
|
||||
);
|
||||
liveStreamScrollBox!.addEventListener("scroll", () => {
|
||||
handleScroll();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={`${classes.snackLog} ${expanded && classes.snackLogExpanded}`}
|
||||
>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="overline">
|
||||
{!log && <span>Build Pending...</span>}
|
||||
{log?.status === "SUCCESS" && (
|
||||
<span
|
||||
style={{
|
||||
color: "#aed581",
|
||||
}}
|
||||
>
|
||||
Build Completed
|
||||
</span>
|
||||
)}
|
||||
{log?.status === "FAIL" && (
|
||||
<span
|
||||
style={{
|
||||
color: "#e57373",
|
||||
}}
|
||||
>
|
||||
Build Failed
|
||||
</span>
|
||||
)}
|
||||
{log?.status === "BUILDING" && <span>Building...</span>}
|
||||
</Typography>
|
||||
<Box>
|
||||
<IconButton
|
||||
aria-label="expand"
|
||||
size="small"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
{expanded ? <CollapseIcon /> : <ExpandIcon />}
|
||||
</IconButton>
|
||||
<IconButton aria-label="open" size="small" onClick={onOpenPanel}>
|
||||
<OpenIcon />
|
||||
</IconButton>
|
||||
<IconButton aria-label="close" size="small" onClick={onClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
className={classes.logEntryWrapper}
|
||||
height={"calc(100% - 25px)"}
|
||||
id="live-stream-scroll-box-snack"
|
||||
>
|
||||
{log && (
|
||||
<>
|
||||
{logs?.map((log, index) => {
|
||||
return <LogRow logRecord={log} index={index} key={index} />;
|
||||
})}
|
||||
<div ref={liveStreamingRef} id="live-stream-target-snack">
|
||||
{status === "BUILDING" && (
|
||||
<CircularProgress
|
||||
className={classes.logPanelProgress}
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ height: 10 }} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableLogs({ requestSnackLog }) {
|
||||
const router = useRouter();
|
||||
const { tableState } = useFiretableContext();
|
||||
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [panalOpen, setPanelOpen] = useState(false);
|
||||
const [snackOpen, setSnackOpen] = useState(false);
|
||||
const [tabIndex, setTabIndex] = React.useState(0);
|
||||
const [activeLogTimestamp, setActiveLogTimestamp] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (requestSnackLog > 0) {
|
||||
setTimeout(() => {
|
||||
setActiveLogTimestamp(requestSnackLog);
|
||||
setSnackOpen(true);
|
||||
}, 500);
|
||||
}
|
||||
}, [requestSnackLog]);
|
||||
|
||||
const tableCollection = decodeURIComponent(router.match.params.id);
|
||||
const ftBuildStreamID =
|
||||
@@ -253,21 +402,20 @@ export default function TableLogs() {
|
||||
path: `${ftBuildStreamID}/ftBuildLogs`,
|
||||
orderBy: [{ key: "startTimeStamp", direction: "desc" }],
|
||||
});
|
||||
const latestStatus = collectionState?.rows?.[0]?.status;
|
||||
const latestLog = collectionState?.rows?.[0];
|
||||
const latestStatus = latestLog?.status;
|
||||
const latestActiveLog =
|
||||
latestLog?.startTimeStamp > activeLogTimestamp ? latestLog : null;
|
||||
|
||||
const handleTabChange = (event, newValue) => {
|
||||
setTabIndex(newValue);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableHeaderButton
|
||||
title="Build Logs"
|
||||
onClick={() => setOpen(true)}
|
||||
onClick={() => setPanelOpen(true)}
|
||||
icon={
|
||||
<>
|
||||
{latestStatus === "BUILDING" && <CircularProgress size={20} />}
|
||||
@@ -278,9 +426,21 @@ export default function TableLogs() {
|
||||
}
|
||||
/>
|
||||
|
||||
{open && !!tableState && (
|
||||
{snackOpen && (
|
||||
<SnackLog
|
||||
log={latestActiveLog}
|
||||
onClose={() => setSnackOpen(false)}
|
||||
onOpenPanel={() => {
|
||||
setPanelOpen(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{panalOpen && !!tableState && (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
onClose={() => {
|
||||
setPanelOpen(false);
|
||||
}}
|
||||
maxWidth="xl"
|
||||
fullWidth
|
||||
title={
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
@@ -86,6 +86,11 @@ export default function TableHeader({
|
||||
|
||||
const { currentUser } = useAppContext();
|
||||
const { tableActions, tableState, userClaims } = useFiretableContext();
|
||||
const [snackCount, setSnackCount] = useState(0);
|
||||
|
||||
const openSnackLog = (requestTimestamp) => {
|
||||
setSnackCount(requestTimestamp);
|
||||
};
|
||||
|
||||
const hasDerivatives =
|
||||
tableState &&
|
||||
@@ -210,7 +215,13 @@ export default function TableHeader({
|
||||
|
||||
{userClaims?.roles?.includes("ADMIN") && (
|
||||
<Grid item>
|
||||
<Sparks />
|
||||
<Sparks requestSnackLog={openSnackLog} />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{userClaims?.roles?.includes("ADMIN") && (
|
||||
<Grid item>
|
||||
<TableLogs requestSnackLog={snackCount} />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
@@ -220,10 +231,6 @@ export default function TableHeader({
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item>
|
||||
<TableLogs />
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<TableSettings />
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user