diff --git a/ft_build/compiler/index.ts b/ft_build/compiler/index.ts index ea9dce16..2a016f24 100644 --- a/ft_build/compiler/index.ts +++ b/ft_build/compiler/index.ts @@ -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); diff --git a/ft_build/compiler/loader.ts b/ft_build/compiler/loader.ts index 7bc6293d..530f6242 100644 --- a/ft_build/compiler/loader.ts +++ b/ft_build/compiler/loader.ts @@ -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( diff --git a/ft_build/index.ts b/ft_build/index.ts index 5e811f68..0d91234a 100644 --- a/ft_build/index.ts +++ b/ft_build/index.ts @@ -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, }); diff --git a/ft_build/utils.ts b/ft_build/utils.ts index a0caef8b..334ea473 100644 --- a/ft_build/utils.ts +++ b/ft_build/utils.ts @@ -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(), + }); + }, }; } diff --git a/www/src/components/Table/TableHeader/TableLogs.tsx b/www/src/components/Table/TableHeader/TableLogs.tsx index b453ec93..06a9ff46 100644 --- a/www/src/components/Table/TableHeader/TableLogs.tsx +++ b/www/src/components/Table/TableHeader/TableLogs.tsx @@ -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} - {logString.replaceAll("\\n", "\n").replaceAll("\\t", "\t")} + + {moment(logRecord.timestamp).format("LTS")} + + {" "} + + {logRecord.log.replaceAll("\\n", "\n").replaceAll("\\t", "\t")} + ); } 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 && ( {logs?.map((log, index) => { - return ; + return ; })} )} @@ -166,11 +186,23 @@ export default function TableLogs() { onChange={handleTabChange} className={classes.tabs} > - {collectionState.rows.map((value, index) => ( + {collectionState.rows.map((logEntry, index) => ( + + {moment(logEntry.startTimeStamp).format( + "MMMM D YYYY h:mm:ssa" + )} + + + {logEntry.status === "BUILDING" && ( + + )} + {logEntry.status === "SUCCESS" && } + + + } {...a11yProps(index)} /> ))} @@ -180,6 +212,7 @@ export default function TableLogs() { value={tabIndex} index={index} logs={logEntry?.fullLog} + status={logEntry?.status} /> ))}