ft-build stream: individual log timestamp; status

This commit is contained in:
Bobby
2021-06-17 22:28:19 +10:00
parent 5b55b9f9f3
commit 433fdaeac5
5 changed files with 125 additions and 48 deletions

View File

@@ -16,16 +16,18 @@ export default async function generateConfig(
streamLogger
).then(async (success) => {
if (!success) {
await streamLogger(`generateConfigFromTableSchema failed to complete`);
await streamLogger.info(
`generateConfigFromTableSchema failed to complete`
);
return false;
}
await streamLogger(`generateConfigFromTableSchema done`);
await streamLogger.info(`generateConfigFromTableSchema done`);
const configFile = fs.readFileSync(
path.resolve(__dirname, "../functions/src/functionConfig.ts"),
"utf-8"
);
await streamLogger(`configFile: ${JSON.stringify(configFile)}`);
await streamLogger.info(`configFile: ${JSON.stringify(configFile)}`);
const requiredDependencies = configFile.match(
/(?<=(require\(("|'))).*?(?=("|')\))/g
);
@@ -39,7 +41,7 @@ export default async function generateConfig(
return false;
}
}
await streamLogger(
await streamLogger.info(
`requiredDependencies: ${JSON.stringify(requiredDependencies)}`
);
@@ -54,7 +56,7 @@ export default async function generateConfig(
streamLogger
)
);
await streamLogger(
await streamLogger.info(
`isFunctionConfigValid: ${JSON.stringify(isFunctionConfigValid)}`
);
if (!isFunctionConfigValid) {
@@ -63,7 +65,9 @@ export default async function generateConfig(
const { sparksConfig } = require("../functions/src/functionConfig.js");
const requiredSparks = sparksConfig.map((s: any) => s.type);
await streamLogger(`requiredSparks: ${JSON.stringify(requiredSparks)}`);
await streamLogger.info(
`requiredSparks: ${JSON.stringify(requiredSparks)}`
);
for (const lib of requiredSparks) {
const success = await addSparkLib(lib, user, streamLogger);

View File

@@ -9,15 +9,17 @@ export const generateConfigFromTableSchema = async (
user: admin.auth.UserRecord,
streamLogger
) => {
await streamLogger("getting schema...");
await streamLogger.info("getting schema...");
const schemaDoc = await db.doc(schemaDocPath).get();
const schemaData = schemaDoc.data();
if (!schemaData) throw new Error("no schema found");
await streamLogger(`schemaData: ${JSON.stringify(schemaData)}`);
await streamLogger.info(`schemaData: ${JSON.stringify(schemaData)}`);
const derivativeColumns = Object.values(schemaData.columns).filter(
(col: any) => col.type === "DERIVATIVE"
);
await streamLogger(`derivativeColumns: ${JSON.stringify(derivativeColumns)}`);
await streamLogger.info(
`derivativeColumns: ${JSON.stringify(derivativeColumns)}`
);
const derivativesConfig = `[${derivativeColumns.reduce(
(acc, currColumn: any) => {
if (
@@ -41,12 +43,14 @@ export const generateConfigFromTableSchema = async (
},
""
)}]`;
await streamLogger(`derivativesConfig: ${JSON.stringify(derivativesConfig)}`);
await streamLogger.info(
`derivativesConfig: ${JSON.stringify(derivativesConfig)}`
);
const initializableColumns = Object.values(
schemaData.columns
).filter((col: any) => Boolean(col.config?.defaultValue));
await streamLogger(
await streamLogger.info(
`initializableColumns: ${JSON.stringify(initializableColumns)}`
);
const initializeConfig = `[${initializableColumns.reduce(
@@ -73,7 +77,9 @@ export const generateConfigFromTableSchema = async (
},
""
)}]`;
await streamLogger(`initializeConfig: ${JSON.stringify(initializeConfig)}`);
await streamLogger.info(
`initializeConfig: ${JSON.stringify(initializeConfig)}`
);
const documentSelectColumns = Object.values(schemaData.columns).filter(
(col: any) => col.type === "DOCUMENT_SELECT" && col.config?.trackedFields
);
@@ -87,12 +93,12 @@ export const generateConfigFromTableSchema = async (
},
""
)}]`;
await streamLogger(
await streamLogger.info(
`documentSelectColumns: ${JSON.stringify(documentSelectColumns)}`
);
const sparksConfig = parseSparksConfig(schemaData.sparks, user);
await streamLogger(`sparksConfig: ${JSON.stringify(sparksConfig)}`);
await streamLogger.info(`sparksConfig: ${JSON.stringify(sparksConfig)}`);
const collectionType = schemaDocPath.includes("subTables")
? "subCollection"
@@ -144,7 +150,7 @@ export const generateConfigFromTableSchema = async (
default:
break;
}
await streamLogger(`collectionType: ${JSON.stringify(collectionType)}`);
await streamLogger.info(`collectionType: ${JSON.stringify(collectionType)}`);
// generate field types from table meta data
const fieldTypes = JSON.stringify(
@@ -160,7 +166,7 @@ export const generateConfigFromTableSchema = async (
};
}, {})
);
await streamLogger(`fieldTypes: ${JSON.stringify(fieldTypes)}`);
await streamLogger.info(`fieldTypes: ${JSON.stringify(fieldTypes)}`);
const exports: any = {
fieldTypes,
@@ -171,12 +177,12 @@ export const generateConfigFromTableSchema = async (
documentSelectConfig,
sparksConfig,
};
await streamLogger(`exports: ${JSON.stringify(exports)}`);
await streamLogger.info(`exports: ${JSON.stringify(exports)}`);
const fileData = Object.keys(exports).reduce((acc, currKey) => {
return `${acc}\nexport const ${currKey} = ${exports[currKey]}`;
}, ``);
await streamLogger(`fileData: ${JSON.stringify(fileData)}`);
await streamLogger.info(`fileData: ${JSON.stringify(fileData)}`);
const path = require("path");
fs.writeFileSync(

View File

@@ -88,8 +88,8 @@ app.post("/", jsonParser, async (req: any, res: any) => {
});
}
const streamLogger = await createStreamLogger(configPath, Date.now());
await streamLogger("streamLogger created");
const streamLogger = await createStreamLogger(configPath);
await streamLogger.info("streamLogger created");
const success = await generateConfig(configPath, user, streamLogger);
if (!success) {
@@ -100,7 +100,7 @@ app.post("/", jsonParser, async (req: any, res: any) => {
});
return;
}
await streamLogger("generateConfig success");
await streamLogger.info("generateConfig success");
let hasEnvError = false;
@@ -134,7 +134,7 @@ app.post("/", jsonParser, async (req: any, res: any) => {
commandErrorHandler({ user }, streamLogger)
);
await streamLogger(`build complete`);
await streamLogger.success();
res.send({
success: true,
});

View File

@@ -26,7 +26,7 @@ function commandErrorHandler(
streamLogger
) {
return async function (error, stdout, stderr) {
await streamLogger(stdout);
await streamLogger.info(stdout);
if (!error) {
return;
@@ -93,26 +93,60 @@ function parseSparksConfig(
return "[]";
}
async function createStreamLogger(
tableConfigPath: string,
startTimeStamp: number
) {
const fullLog: string[] = [];
async function createStreamLogger(tableConfigPath: string) {
const startTimeStamp = Date.now();
const fullLog: {
log: string;
level: "info" | "error";
timestamp: number;
}[] = [];
const logRef = db
.doc(tableConfigPath)
.collection("ftBuildLogs")
.doc(startTimeStamp.toString());
await logRef.set({ startTimeStamp });
await logRef.set({ startTimeStamp, status: "BUILDING" });
console.log(
`streamLogger created. tableConfigPath: ${tableConfigPath}, startTimeStamp: ${startTimeStamp}`
);
return async (log: string) => {
console.log(log);
fullLog.push(log);
await logRef.update({
fullLog,
});
return {
info: async (log: string) => {
console.log(log);
fullLog.push({
log,
level: "info",
timestamp: Date.now(),
});
await logRef.update({
fullLog,
});
},
error: async (log: string) => {
console.error(log);
fullLog.push({
log,
level: "error",
timestamp: Date.now(),
});
await logRef.update({
fullLog,
});
},
success: async () => {
console.log("streamLogger marked as SUCCESS");
await logRef.update({
status: "SUCCESS",
successTimeStamp: Date.now(),
});
},
fail: async () => {
console.log("streamLogger marked as FAIL");
await logRef.update({
status: "FAIL",
failTimeStamp: Date.now(),
});
},
};
}

View File

@@ -9,14 +9,18 @@ import _find from "lodash/find";
import _sortBy from "lodash/sortBy";
import moment from "moment";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import { Chip } from "@material-ui/core";
import {
Chip,
CircularProgress,
Typography,
Box,
Tabs,
Tab,
} from "@material-ui/core";
import Modal from "components/Modal";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
import LogsIcon from "@material-ui/icons/QueryBuilder";
import SuccessIcon from "@material-ui/icons/CheckCircleOutline";
import TableHeaderButton from "./TableHeaderButton";
import Ansi from "ansi-to-react";
@@ -39,6 +43,12 @@ const useStyles = makeStyles((theme) => ({
tabs: {
borderRight: `1px solid ${theme.palette.divider}`,
},
tab: {
display: "flex",
flexWrap: "nowrap",
alignItems: "center",
justifyItems: "center",
},
logPanel: {
width: "100%",
@@ -59,15 +69,19 @@ const useStyles = makeStyles((theme) => ({
whiteSpace: "break-spaces",
userSelect: "text",
},
logTimestamp: {
color: "green",
},
}));
LogPanel.propTypes = {
logs: PropTypes.array,
status: PropTypes.string,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function LogRow({ logString, index }) {
function LogRow({ logRecord, index }) {
const classes = useStyles();
return (
@@ -76,14 +90,20 @@ function LogRow({ logString, index }) {
{index}
</Typography>
<Typography variant="body2" className={classes.logEntry}>
<Ansi>{logString.replaceAll("\\n", "\n").replaceAll("\\t", "\t")}</Ansi>
<Ansi className={classes.logTimestamp}>
{moment(logRecord.timestamp).format("LTS")}
</Ansi>
{" "}
<Ansi>
{logRecord.log.replaceAll("\\n", "\n").replaceAll("\\t", "\t")}
</Ansi>
</Typography>
</Box>
);
}
function LogPanel(props) {
const { logs, value, index, ...other } = props;
const { logs, status, value, index, ...other } = props;
const classes = useStyles();
return (
@@ -98,7 +118,7 @@ function LogPanel(props) {
{value === index && (
<Box p={3} className={classes.logEntryWrapper}>
{logs?.map((log, index) => {
return <LogRow logString={log} index={index} />;
return <LogRow logRecord={log} index={index} />;
})}
</Box>
)}
@@ -166,11 +186,23 @@ export default function TableLogs() {
onChange={handleTabChange}
className={classes.tabs}
>
{collectionState.rows.map((value, index) => (
{collectionState.rows.map((logEntry, index) => (
<Tab
label={moment(value.startTimeStamp).format(
"MMMM D YYYY h:mm:ssa"
)}
label={
<Box className={classes.tab}>
<Box>
{moment(logEntry.startTimeStamp).format(
"MMMM D YYYY h:mm:ssa"
)}
</Box>
<Box>
{logEntry.status === "BUILDING" && (
<CircularProgress size={24} />
)}
{logEntry.status === "SUCCESS" && <SuccessIcon />}
</Box>
</Box>
}
{...a11yProps(index)}
/>
))}
@@ -180,6 +212,7 @@ export default function TableLogs() {
value={tabIndex}
index={index}
logs={logEntry?.fullLog}
status={logEntry?.status}
/>
))}
</div>