Checkpoint - nearing releasable!

This commit is contained in:
2023-03-03 10:07:29 -06:00
parent f766f17a92
commit ed17a311af
2 changed files with 116 additions and 58 deletions

View File

@ -28,13 +28,16 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {TablePagination} from "@mui/material"; import {TablePagination} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";
import LinearProgress from "@mui/material/LinearProgress"; import LinearProgress from "@mui/material/LinearProgress";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import {DataGridPro} from "@mui/x-data-grid-pro"; import {DataGridPro, GridSortModel} from "@mui/x-data-grid-pro";
import FormData from "form-data"; import FormData from "form-data";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import DataGridUtils from "qqq/utils/DataGridUtils"; import DataGridUtils from "qqq/utils/DataGridUtils";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props interface Props
{ {
@ -51,33 +54,62 @@ const qController = Client.getInstance();
function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
{ {
const [statusString, setStatusString] = useState("Calculating statistics..."); const [statusString, setStatusString] = useState("Calculating statistics...");
const [isLoaded, setIsLoaded] = useState(false); const [loading, setLoading] = useState(true);
const [valueCounts, setValueCounts] = useState(null as QRecord[]); const [valueCounts, setValueCounts] = useState(null as QRecord[]);
const [statsRecord, setStatsRecord] = useState(null as QRecord);
const [orderBy, setOrderBy] = useState(null as string);
const [statsFields, setStatsFields] = useState([] as QFieldMetaData[]);
const [countDistinct, setCountDistinct] = useState(null as number); const [countDistinct, setCountDistinct] = useState(null as number);
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
useEffect(() => useEffect(() =>
{ {
if(!loading)
{
return;
}
(async () => (async () =>
{ {
const formData = new FormData(); const formData = new FormData();
formData.append("tableName", tableMetaData.name); formData.append("tableName", tableMetaData.name);
formData.append("fieldName", fieldMetaData.name); formData.append("fieldName", fieldMetaData.name);
formData.append("filterJSON", JSON.stringify(filter)); formData.append("filterJSON", JSON.stringify(filter));
if(orderBy)
{
formData.append("orderBy", orderBy);
}
const processResult = await qController.processRun("tableStats", formData); const processResult = await qController.processRun("tableStats", formData);
setStatusString(null) setStatusString(null)
if (processResult instanceof QJobError) if (processResult instanceof QJobError)
{ {
const jobError = processResult as QJobError; const jobError = processResult as QJobError;
// todo setErrorAlert(); setStatusString("Error fetching column stats: " + jobError.error);
console.error("Error fetching column stats" + jobError.error); setLoading(false);
} }
else else
{ {
const result = processResult as QJobComplete; const result = processResult as QJobComplete;
setCountDistinct(result.values.countDistinct);
const statFieldObjects = result.values.statsFields;
if(statFieldObjects && statFieldObjects.length)
{
const newStatsFields = [] as QFieldMetaData[];
for(let i = 0; i<statFieldObjects.length; i++)
{
newStatsFields.push(new QFieldMetaData(statFieldObjects[i]))
}
setStatsFields(newStatsFields);
}
let qRecord = new QRecord(result.values.statsRecord);
setStatsRecord(qRecord);
if(qRecord.values.has("countDistinct"))
{
setCountDistinct(qRecord.values.get("countDistinct"));
}
const valueCounts = [] as QRecord[]; const valueCounts = [] as QRecord[];
for(let i = 0; i < result.values.valueCounts.length; i++) for(let i = 0; i < result.values.valueCounts.length; i++)
@ -95,43 +127,33 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
const {rows, columnsToRender} = DataGridUtils.makeRows(valueCounts, fakeTableMetaData); const {rows, columnsToRender} = DataGridUtils.makeRows(valueCounts, fakeTableMetaData);
const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, columnsToRender); const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, columnsToRender);
columns[0].width = 200;
columns[1].sortComparator = (v1, v2): number => columns[1].width = 200;
{
const n1 = parseInt(v1.replaceAll(",", ""));
const n2 = parseInt(v2.replaceAll(",", ""));
return (n1 - n2);
}
setRows(rows); setRows(rows);
setColumns(columns); setColumns(columns);
setLoading(false);
setIsLoaded(true);
} }
})(); })();
}, []); }, [loading]);
// @ts-ignore
const defaultLabelDisplayedRows = ({from, to, count}) =>
{
// todo - not done
return ("Showing stuff");
};
function CustomPagination() function CustomPagination()
{ {
return ( return (
<TablePagination <Box pr={3}>
component="div" {rows && rows.length && countDistinct && rows.length < countDistinct ? <span>Showing the first {rows.length.toLocaleString()} of {countDistinct.toLocaleString()} values</span> : <></>}
page={1} {rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length == 1 ? <span>Showing the only value</span> : <></>}
count={1} {rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length > 1 ? <span>Showing all {rows.length.toLocaleString()} values</span> : <></>}
rowsPerPage={1000} </Box>
onPageChange={null}
labelDisplayedRows={defaultLabelDisplayedRows}
/>
); );
} }
const refresh = () =>
{
setLoading(true)
setStatusString("Refreshing...")
}
function Loading() function Loading()
{ {
return ( return (
@ -139,43 +161,72 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
); );
} }
const handleSortChange = (gridSort: GridSortModel) =>
{
if (gridSort && gridSort.length > 0)
{
console.log("Sort: ", gridSort[0]);
setOrderBy(`${gridSort[0].field}.${gridSort[0].sort}`);
refresh();
}
};
return ( return (
<Box> <Box>
<Box p={3} display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start"> <Box p={3} display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start">
<Typography variant="h5" pb={3}> <Typography variant="h5" pb={3}>
Column Statistics for {tableMetaData.label}: {fieldMetaData.label} Column Statistics for {fieldMetaData.label}
<Typography fontSize={14}> <Typography fontSize={14}>
{statusString} {statusString ?? <>&nbsp;</>}
</Typography> </Typography>
</Typography> </Typography>
<Button onClick={() => refresh()}>
Refresh
</Button>
</Box> </Box>
<Box px={3} fontSize="1rem"> <Grid container>
<div> <Grid item xs={8}>
<div className="fieldLabel">Distinct Values: </div> <div className="fieldValue">{Number(countDistinct).toLocaleString()}</div> <Box sx={{overflow: "auto", height: "calc( 100vh - 18rem )", position: "relative"}} px={3}>
</div> <DataGridPro
</Box> components={{LoadingOverlay: Loading, Pagination: CustomPagination}}
rows={rows}
<Box sx={{overflow: "auto", height: "calc( 100vh - 19rem )", position: "relative"}} px={3}> disableSelectionOnClick
<DataGridPro columns={columns}
components={{LoadingOverlay: Loading, Pagination: CustomPagination}} loading={loading}
rows={rows} rowBuffer={10}
disableSelectionOnClick getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
columns={columns} sortingMode={"server"}
loading={!isLoaded} onSortModelChange={handleSortChange}
rowBuffer={10} sortingOrder={["desc", "asc"]}
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")} pagination={true}
initialState={{ paginationMode={"server"}
sorting: { initialState={{
sortModel: [ sorting: {
{ sortModel: [
field: "count", {
sort: "desc", field: "count",
sort: "desc",
},
],
}, },
], }}
}, />
}} </Box>
/> </Grid>
</Box> <Grid item xs={4} sx={{whiteSpace: "nowrap", overflowX: "auto"}}>
<Box px={3} fontSize="1rem">
{
statsFields && statsFields.map((field) =>
(
<Box key={field.name} pb={1}>
<div className="fieldLabel">{field.label}: </div>
<div className="fieldValue">{ValueUtils.getValueForDisplay(field, statsRecord?.values.get(field.name), statsRecord?.displayValues.get(field.name))}</div>
</Box>
))
}
</Box>
</Grid>
</Grid>
</Box>); </Box>);
} }

View File

@ -57,6 +57,13 @@ export default class DataGridUtils
if(!row["id"]) if(!row["id"])
{ {
row["id"] = record.values.get(tableMetaData.primaryKeyField) ?? row[tableMetaData.primaryKeyField]; row["id"] = record.values.get(tableMetaData.primaryKeyField) ?? row[tableMetaData.primaryKeyField];
if(row["id"] === null || row["id"] === undefined)
{
/////////////////////////////////////////////////////////////////////////////////////////
// DataGrid gets very upset about a null or undefined here, so, try to make it happier //
/////////////////////////////////////////////////////////////////////////////////////////
row["id"] = "--";
}
} }
rows.push(row); rows.push(row);