Fixes for column stats with joins; fix some js console warnings;

This commit is contained in:
2023-05-04 10:54:15 -05:00
parent 061ddc7f4a
commit cbaeb3cce4
4 changed files with 116 additions and 66 deletions

View File

@ -294,13 +294,8 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
//////////////////////////////////////////////////////////////////
// we don't want this job to go async, so, pass a large timeout //
//////////////////////////////////////////////////////////////////
formData.append("_qStepTimeoutMillis", 60 * 1000);
const formDataHeaders = {
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
};
const processResult = await qController.processInit(processName, formData, formDataHeaders);
formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000);
const processResult = await qController.processInit(processName, formData, qController.defaultMultipartFormDataHeaders());
if (processResult instanceof QJobError)
{
const jobError = processResult as QJobError;
@ -346,7 +341,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
onClose={closeSavedFiltersMenu}
keepMounted
>
<MenuItem sx={{width: "300px"}}><b>Filter Actions</b></MenuItem>
<MenuItem sx={{width: "300px"}} disabled style={{"opacity": "initial"}}><b>Filter Actions</b></MenuItem>
{
hasStorePermission &&
<MenuItem onClick={() => handleDropdownOptionClick(SAVE_OPTION)}>
@ -382,7 +377,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
</MenuItem>
}
<Divider/>
<MenuItem><b>Your Filters</b></MenuItem>
<MenuItem disabled style={{"opacity": "initial"}}><b>Your Filters</b></MenuItem>
{
savedFilters && savedFilters.length > 0 ? (
savedFilters.map((record: QRecord, index: number) =>
@ -413,7 +408,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
{currentSavedFilter.values.get("label")}
{
filterIsModified && (
<Tooltip sx={{cursor: "pointer"}} title={"The current filter has been modified, click \"Save...\" to save the changes."}>
<Tooltip sx={{cursor: "pointer"}} title={"The current filter has been modified. Click \"Save...\" to save the changes."}>
<FiberManualRecord sx={{color: "orange", paddingLeft: "2px", paddingTop: "4px"}} />
</Tooltip>
)
@ -430,6 +425,13 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
onClose={handleSaveFilterPopupClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
onKeyPress={(e) =>
{
if (e.key == "Enter")
{
handleFilterDialogButtonOnClick();
}
}}
>
{
currentSavedFilter ? (
@ -479,7 +481,6 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
):(
isDeleteFilter ? (
<Box>Are you sure you want to delete the filter {`'${currentSavedFilter?.values.get("label")}'`}?</Box>
):(
<Box>Are you sure you want to update the filter {`'${currentSavedFilter?.values.get("label")}'`} with the current filter criteria?</Box>
)

View File

@ -44,6 +44,7 @@ interface Props
{
tableMetaData: QTableMetaData;
fieldMetaData: QFieldMetaData;
fieldTableName: string;
filter: QQueryFilter;
}
@ -52,7 +53,7 @@ ColumnStats.defaultProps = {
const qController = Client.getInstance();
function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Props): JSX.Element
{
const [statusString, setStatusString] = useState("Calculating statistics...");
const [loading, setLoading] = useState(true);
@ -73,9 +74,11 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
(async () =>
{
const fullFieldName = (fieldTableName == tableMetaData.name ? "" : `${fieldTableName}.`) + fieldMetaData.name;
const formData = new FormData();
formData.append("tableName", tableMetaData.name);
formData.append("fieldName", fieldMetaData.name);
formData.append("fieldName", fullFieldName);
formData.append("filterJSON", JSON.stringify(filter));
if(orderBy)
{
@ -115,7 +118,16 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
const valueCounts = [] as QRecord[];
for(let i = 0; i < result.values.valueCounts.length; i++)
{
valueCounts.push(new QRecord(result.values.valueCounts[i]));
let valueRecord = new QRecord(result.values.valueCounts[i]);
////////////////////////////////////////////////////////////////////////////////////////////////
// for a field from a join, the backend will have sent it as table.field (e.g., lineItem.sku) //
// but we'll have a "better time" in the rest of this code if we have it as just the field //
// name, so, copy it there... //
////////////////////////////////////////////////////////////////////////////////////////////////
valueRecord.displayValues.set(fieldMetaData.name, valueRecord.displayValues.get(fullFieldName));
valueRecord.values.set(fieldMetaData.name, valueRecord.values.get(fullFieldName));
valueCounts.push(valueRecord);
}
setValueCounts(valueCounts);
@ -128,6 +140,7 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element
const rows = DataGridUtils.makeRows(valueCounts, fakeTableMetaData);
const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, null, null, "bySection");
columns.forEach((c) =>
{
c.width = 200;

View File

@ -19,14 +19,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
import {Alert, Box, Collapse, TablePagination} from "@mui/material";
@ -194,6 +195,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter);
const [columnStatsFieldName, setColumnStatsFieldName] = useState(null as string);
const [columnStatsField, setColumnStatsField] = useState(null as QFieldMetaData);
const [columnStatsFieldTableName, setColumnStatsFieldTableName] = useState(null as string)
const [filterForColumnStats, setFilterForColumnStats] = useState(null as QQueryFilter);
const instance = useRef({timer: null});
@ -280,17 +283,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
const formData = new FormData();
formData.append("id", currentSavedFilterId);
//////////////////////////////////////////////////////////////////
// we don't want this job to go async, so, pass a large timeout //
//////////////////////////////////////////////////////////////////
formData.append("_qStepTimeoutMillis", 60 * 1000);
const formDataHeaders = {
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
};
const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders);
formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000);
const processResult = await qController.processInit("querySavedFilter", formData, qController.defaultMultipartFormDataHeaders());
if (processResult instanceof QJobError)
{
const jobError = processResult as QJobError;
@ -1065,6 +1059,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
setColumnStatsFieldName(null);
setColumnStatsFieldTableName(null);
setColumnStatsField(null);
};
const closeModalProcess = (event: object, reason: string) =>
@ -1140,8 +1136,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
that match your query, because you have included fields from other tables which may have
more than one record associated with each {tableMetaData?.label}.
</>
let distinctPart = isJoinMany(tableMetaData, getVisibleJoinTables()) ? (<Box display="inline" textAlign="right">
&nbsp;({distinctRecords} distinct<CustomWidthTooltip title={tooltipHTML}>
let distinctPart = isJoinMany(tableMetaData, getVisibleJoinTables()) ? (<Box display="inline" component="span" textAlign="right">
&nbsp;({safeToLocaleString(distinctRecords)} distinct<CustomWidthTooltip title={tooltipHTML}>
<IconButton sx={{p: 0, pl: 0.25, mb: 0.25}}><Icon fontSize="small" sx={{fontSize: "1.125rem !important", color: "#9f9f9f"}}>info_outlined</Icon></IconButton>
</CustomWidthTooltip>
)
@ -1170,14 +1166,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
return (loading ? "Counting..." : "No rows");
}
return <>
return <span>
Showing {from.toLocaleString()} to {to.toLocaleString()} of
{
count == -1 ?
<>more than {to.toLocaleString()}</>
: <> {count.toLocaleString()}{distinctPart}</>
}
</>;
</span>;
}
else
{
@ -1215,6 +1211,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
if (selectedSavedFilterId != null)
{
const qRecord = await fetchSavedFilter(selectedSavedFilterId);
setCurrentSavedFilter(qRecord); // this fixed initial load not showing filter name
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null);
handleFilterChange(models.filter);
handleSortChange(models.sort);
@ -1233,17 +1231,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
let qRecord = null;
const formData = new FormData();
formData.append("id", filterId);
//////////////////////////////////////////////////////////////////
// we don't want this job to go async, so, pass a large timeout //
//////////////////////////////////////////////////////////////////
formData.append("_qStepTimeoutMillis", 60 * 1000);
const formDataHeaders = {
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
};
const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders);
formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000);
const processResult = await qController.processInit("querySavedFilter", formData, qController.defaultMultipartFormDataHeaders());
if (processResult instanceof QJobError)
{
const jobError = processResult as QJobError;
@ -1293,6 +1282,25 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
setFilterForColumnStats(buildQFilter(tableMetaData, filterModel));
setColumnStatsFieldName(column.field);
if(column.field.indexOf(".") > -1)
{
const nameParts = column.field.split(".", 2);
for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++)
{
const join = tableMetaData?.exposedJoins[i];
if(join?.joinTable.name == nameParts[0])
{
setColumnStatsField(join.joinTable.fields.get(nameParts[1]));
setColumnStatsFieldTableName(nameParts[0]);
}
}
}
else
{
setColumnStatsField(tableMetaData.fields.get(column.field));
setColumnStatsFieldTableName(tableMetaData.name);
}
};
const CustomColumnMenu = forwardRef<HTMLUListElement, GridColumnMenuProps>(
@ -1439,6 +1447,15 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
);
const safeToLocaleString = (n: Number): string =>
{
if(n != null && n != undefined)
{
return (n.toLocaleString());
}
return ("");
}
function CustomToolbar()
{
const handleMouseDown: GridEventListener<"cellMouseDown"> = (
@ -1473,15 +1490,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const joinIsMany = isJoinMany(tableMetaData, visibleJoinTables);
const safeToLocaleString = (n: Number): string =>
{
if(n != null && n != undefined)
{
return (n.toLocaleString());
}
return ("");
}
const selectionMenuOptions: string[] = [];
selectionMenuOptions.push(`This page (${safeToLocaleString(distinctRecordsOnPageCount)} ${joinIsMany ? "distinct " : ""}record${distinctRecordsOnPageCount == 1 ? "" : "s"})`);
selectionMenuOptions.push(`Full query result (${joinIsMany ? safeToLocaleString(distinctRecords) + ` distinct record${distinctRecords == 1 ? "" : "s"}` : safeToLocaleString(totalRecords) + ` record${totalRecords == 1 ? "" : "s"}`})`);
@ -1552,9 +1560,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
Refresh
</Button>
</div>
<GridToolbarColumnsButton nonce={undefined} onResize={undefined} onResizeCapture={undefined} />
{/* @ts-ignore */}
<GridToolbarColumnsButton nonce={undefined} />
<div style={{position: "relative"}}>
<GridToolbarFilterButton nonce={undefined} onResize={undefined} onResizeCapture={undefined} />
{/* @ts-ignore */}
<GridToolbarFilterButton nonce={undefined} />
{
hasValidFilters && (
@ -1567,7 +1577,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
if (e.key == "Enter")
{
setShowClearFiltersWarning(false)
navigate(metaData.getTablePathByName(tableName));
handleFilterChange({items: []} as GridFilterModel);
}
}}>
@ -1580,7 +1589,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
<QSaveButton label="Yes" iconName="check" disabled={false} onClickHandler={() =>
{
setShowClearFiltersWarning(false);
navigate(metaData.getTablePathByName(tableName));
handleFilterChange({items: []} as GridFilterModel);
}}/>
</DialogActions>
@ -1588,8 +1596,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
</div>
)
}
<GridToolbarDensitySelector nonce={undefined} onResize={undefined} onResizeCapture={undefined} />
<GridToolbarExportContainer nonce={undefined} onResize={undefined} onResizeCapture={undefined}>
{/* @ts-ignore */}
<GridToolbarDensitySelector nonce={undefined} />
{/* @ts-ignore */}
<GridToolbarExportContainer nonce={undefined}>
<ExportMenuItem format="csv" />
<ExportMenuItem format="xlsx" />
<ExportMenuItem format="json" />
@ -1671,22 +1681,22 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
if (menuItems.length > 0)
{
menuItems.push(<Divider />);
menuItems.push(<Divider key="divider" />);
}
};
const menuItems: JSX.Element[] = [];
if (table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{
menuItems.push(<MenuItem onClick={bulkLoadClicked}><ListItemIcon><Icon>library_add</Icon></ListItemIcon>Bulk Load</MenuItem>);
menuItems.push(<MenuItem key="bulkLoad" onClick={bulkLoadClicked}><ListItemIcon><Icon>library_add</Icon></ListItemIcon>Bulk Load</MenuItem>);
}
if (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
{
menuItems.push(<MenuItem onClick={bulkEditClicked}><ListItemIcon><Icon>edit</Icon></ListItemIcon>Bulk Edit</MenuItem>);
menuItems.push(<MenuItem key="bulkEdit" onClick={bulkEditClicked}><ListItemIcon><Icon>edit</Icon></ListItemIcon>Bulk Edit</MenuItem>);
}
if (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
{
menuItems.push(<MenuItem onClick={bulkDeleteClicked}><ListItemIcon><Icon>delete</Icon></ListItemIcon>Bulk Delete</MenuItem>);
menuItems.push(<MenuItem key="bulkDelete" onClick={bulkDeleteClicked}><ListItemIcon><Icon>delete</Icon></ListItemIcon>Bulk Delete</MenuItem>);
}
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
@ -1696,7 +1706,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
menuItems.push(<MenuItem key={process.name} onClick={() => processClicked(process)}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
}
menuItems.push(<MenuItem onClick={() => navigate("dev")}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
menuItems.push(<MenuItem key="developerMode" onClick={() => navigate("dev")}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
if (tableProcesses && tableProcesses.length)
{
@ -1711,7 +1721,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
if (menuItems.length === 0)
{
menuItems.push(<MenuItem disabled><ListItemIcon><Icon>block</Icon></ListItemIcon><i>No actions available</i></MenuItem>);
menuItems.push(<MenuItem key="notAvaialableNow" disabled><ListItemIcon><Icon>block</Icon></ListItemIcon><i>No actions available</i></MenuItem>);
}
const renderActionsMenu = (
@ -1898,7 +1908,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}>
<Card sx={{my: 5, mx: "auto", pb: 0, maxWidth: "1024px"}}>
<Box component="div">
<ColumnStats tableMetaData={tableMetaData} fieldMetaData={tableMetaData?.fields.get(columnStatsFieldName)} filter={filterForColumnStats} />
<ColumnStats tableMetaData={tableMetaData} fieldMetaData={columnStatsField} fieldTableName={columnStatsFieldTableName} filter={filterForColumnStats} />
<Box p={3} display="flex" flexDirection="row" justifyContent="flex-end">
<QCancelButton label="Close" onClickHandler={() => closeColumnStats(null, null)} disabled={false} />
</Box>

View File

@ -360,7 +360,7 @@ class FilterUtils
let defaultFilter = {items: []} as GridFilterModel;
let defaultSort = [] as GridSortItem[];
if (tableMetaData.fields !== undefined)
if (tableMetaData && tableMetaData.fields !== undefined)
{
if (filterString != null || (searchParams && searchParams.has("filter")))
{
@ -376,7 +376,33 @@ class FilterUtils
for (let i = 0; i < qQueryFilter?.criteria?.length; i++)
{
const criteria = qQueryFilter.criteria[i];
const field = tableMetaData.fields.get(criteria.fieldName);
let fieldTable = tableMetaData;
let field = null;
if (criteria.fieldName.indexOf(".") > -1)
{
const nameParts = criteria.fieldName.split(".", 2);
for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++)
{
const joinTable = tableMetaData.exposedJoins[i].joinTable;
if (joinTable.name == nameParts[0])
{
fieldTable = joinTable;
field = joinTable.fields.get(nameParts[1]);
break;
}
}
}
else
{
field = tableMetaData.fields.get(criteria.fieldName);
}
if (field == null)
{
console.log("Couldn't find field for filter: " + criteria.fieldName);
continue;
}
let values = criteria.values;
if (field.possibleValueSourceName)
{
@ -388,7 +414,7 @@ class FilterUtils
//////////////////////////////////////////////////////////////////////////////////
if (values && values.length > 0)
{
values = await qController.possibleValues(tableMetaData.name, null, field.name, "", values);
values = await qController.possibleValues(fieldTable.name, null, field.name, "", values);
}
////////////////////////////////////////////