mirror of
https://github.com/rowyio/rowy.git
synced 2026-02-24 04:01:17 +01:00
Merge branch 'develop' of https://github.com/FiretableProject/firetable into develop
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "^3.4.3",
|
||||
"react-scroll-sync": "^0.8.0",
|
||||
"react-usestateref": "^1.0.5",
|
||||
"serve": "^11.3.2",
|
||||
"tinymce": "^5.2.0",
|
||||
"typescript": "^3.7.2",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import useRouter from "hooks/useRouter";
|
||||
import useTable from "hooks/useFiretable/useTable";
|
||||
import { useFiretableContext } from "contexts/FiretableContext";
|
||||
import useStateRef from "react-usestateref";
|
||||
import { db } from "../../../firebase";
|
||||
|
||||
import _camelCase from "lodash/camelCase";
|
||||
import _get from "lodash/get";
|
||||
import _find from "lodash/find";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
import _throttle from "lodash/throttle";
|
||||
import moment from "moment";
|
||||
|
||||
import {
|
||||
@@ -16,12 +19,18 @@ import {
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
IconButton,
|
||||
Link,
|
||||
} 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";
|
||||
@@ -36,6 +45,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,
|
||||
@@ -57,19 +72,23 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: "100%",
|
||||
backgroundColor: "#1E1E1E",
|
||||
},
|
||||
logPanelProgress: {
|
||||
marginLeft: "2em",
|
||||
marginTop: "1em",
|
||||
},
|
||||
logEntryWrapper: {
|
||||
overflowY: "scroll",
|
||||
maxHeight: "100%",
|
||||
},
|
||||
logNumber: {
|
||||
float: "left",
|
||||
width: "3em",
|
||||
width: "2em",
|
||||
textAlign: "right",
|
||||
paddingRight: "1em",
|
||||
},
|
||||
logEntry: {
|
||||
lineBreak: "anywhere",
|
||||
paddingLeft: "3em",
|
||||
paddingLeft: "2em",
|
||||
whiteSpace: "break-spaces",
|
||||
userSelect: "text",
|
||||
},
|
||||
@@ -90,6 +109,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 = {
|
||||
@@ -136,36 +171,236 @@ function LogRow({ logRecord, index }) {
|
||||
}
|
||||
|
||||
function LogPanel(props) {
|
||||
const { logs, status, value, index, ...other } = props;
|
||||
const { logs, status, value, index, isOpen, ...other } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
// useStateRef is necessary to resolve the state syncing issue
|
||||
// https://stackoverflow.com/a/63039797/12208834
|
||||
const [liveStreaming, setLiveStreaming, liveStreamingStateRef] = useStateRef(
|
||||
true
|
||||
);
|
||||
const liveStreamingRef = useRef<any>();
|
||||
const isActive = value === index;
|
||||
|
||||
const handleScroll = _throttle(() => {
|
||||
const target = document.querySelector("#live-stream-target");
|
||||
const scrollBox = document.querySelector("#live-stream-scroll-box");
|
||||
const liveStreamTargetVisible = isTargetInsideBox(target, scrollBox);
|
||||
if (liveStreamTargetVisible !== liveStreamingStateRef.current) {
|
||||
console.log("live streaming:", liveStreamTargetVisible);
|
||||
setLiveStreaming(liveStreamTargetVisible);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
const scrollToLive = () => {
|
||||
const liveStreamTarget = document.querySelector("#live-stream-target");
|
||||
liveStreamTarget?.scrollIntoView?.({
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (liveStreaming && isActive && status === "BUILDING") {
|
||||
if (!liveStreamingRef.current) {
|
||||
scrollToLive();
|
||||
} else {
|
||||
setTimeout(scrollToLive, 100);
|
||||
}
|
||||
}
|
||||
}, [logs, value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
const liveStreamScrollBox = document.querySelector(
|
||||
"#live-stream-scroll-box"
|
||||
);
|
||||
liveStreamScrollBox!.addEventListener("scroll", () => {
|
||||
handleScroll();
|
||||
});
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
hidden={!isActive}
|
||||
id={`vertical-tabpanel-${index}`}
|
||||
aria-labelledby={`vertical-tab-${index}`}
|
||||
className={classes.logPanel}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box p={3} className={classes.logEntryWrapper}>
|
||||
<Box
|
||||
p={3}
|
||||
className={classes.logEntryWrapper}
|
||||
id="live-stream-scroll-box"
|
||||
>
|
||||
{logs?.map((log, index) => {
|
||||
return <LogRow logRecord={log} index={index} />;
|
||||
return <LogRow logRecord={log} index={index} key={index} />;
|
||||
})}
|
||||
<div ref={liveStreamingRef} id="live-stream-target">
|
||||
{status === "BUILDING" && (
|
||||
<CircularProgress
|
||||
className={classes.logPanelProgress}
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ height: 10 }} />
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 [buildURLConfigured, setBuildURLConfigured] = useState(true);
|
||||
const [tabIndex, setTabIndex] = React.useState(0);
|
||||
const [activeLogTimestamp, setActiveLogTimestamp] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
checkBuildURL();
|
||||
}, []);
|
||||
|
||||
const checkBuildURL = async () => {
|
||||
const settingsDoc = await db.doc("/_FIRETABLE_/settings").get();
|
||||
const ftBuildUrl = settingsDoc.get("ftBuildUrl");
|
||||
if (!ftBuildUrl) {
|
||||
setBuildURLConfigured(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (requestSnackLog > 0) {
|
||||
setTimeout(() => {
|
||||
setActiveLogTimestamp(requestSnackLog);
|
||||
setSnackOpen(true);
|
||||
}, 500);
|
||||
}
|
||||
}, [requestSnackLog]);
|
||||
|
||||
const tableCollection = decodeURIComponent(router.match.params.id);
|
||||
const ftBuildStreamID =
|
||||
@@ -182,21 +417,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} />}
|
||||
@@ -207,9 +441,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={
|
||||
@@ -219,7 +465,7 @@ export default function TableLogs() {
|
||||
}
|
||||
children={
|
||||
<>
|
||||
{!latestStatus && (
|
||||
{!latestStatus && buildURLConfigured && (
|
||||
<EmptyState
|
||||
message="No Logs Found"
|
||||
description={
|
||||
@@ -227,6 +473,27 @@ export default function TableLogs() {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!latestStatus && !buildURLConfigured && (
|
||||
<EmptyState
|
||||
message="Need Configuration"
|
||||
description={
|
||||
<>
|
||||
Cloud Run trigger URL not configured. Configuration guide:{" "}
|
||||
<Link
|
||||
href={
|
||||
"https://github.com/AntlerVC/firetable/wiki/Setting-up-cloud-Run-FT-Builder"
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="body2"
|
||||
underline="always"
|
||||
>
|
||||
https://github.com/AntlerVC/firetable/wiki/Setting-up-cloud-Run-FT-Builder
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{latestStatus && (
|
||||
<div className={classes.root}>
|
||||
<Tabs
|
||||
@@ -238,6 +505,7 @@ export default function TableLogs() {
|
||||
>
|
||||
{collectionState.rows.map((logEntry, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
label={
|
||||
<Box className={classes.tab}>
|
||||
<Box>
|
||||
@@ -260,6 +528,7 @@ export default function TableLogs() {
|
||||
</Tabs>
|
||||
{collectionState.rows.map((logEntry, index) => (
|
||||
<LogPanel
|
||||
key={index}
|
||||
value={tabIndex}
|
||||
index={index}
|
||||
logs={logEntry?.fullLog}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12685,6 +12685,11 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-usestateref@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-usestateref/-/react-usestateref-1.0.5.tgz#0b5659217022a7e88087875ec1745730940b7882"
|
||||
integrity sha512-Bia+nJDuhmakaUQR6ztVPvZnGz2cV8ZOh9Tlgpc2iZorwc2Li4xcQUICtMwdAms5N9bH8FnPX+mXn91EsExvvg==
|
||||
|
||||
"react@^16.8.0 || ^17.0.0":
|
||||
version "17.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
|
||||
|
||||
Reference in New Issue
Block a user