/* * QQQ - Low-code Application Framework for Engineers. * Copyright (C) 2021-2023. Kingsrook, LLC * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States * contact@kingsrook.com * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; 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 {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Grid from "@mui/material/Grid"; import Icon from "@mui/material/Icon"; import LinearProgress from "@mui/material/LinearProgress"; import Typography from "@mui/material/Typography"; import {DataGridPro, GridSortModel} from "@mui/x-data-grid-pro"; import FormData from "form-data"; import React, {useEffect, useState} from "react"; import DataGridUtils from "qqq/utils/DataGridUtils"; import HtmlUtils from "qqq/utils/HtmlUtils"; import Client from "qqq/utils/qqq/Client"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; interface Props { tableMetaData: QTableMetaData; fieldMetaData: QFieldMetaData; fieldTableName: string; filter: QQueryFilter; } ColumnStats.defaultProps = { }; const qController = Client.getInstance(); function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Props): JSX.Element { const [statusString, setStatusString] = useState("Calculating statistics..."); const [loading, setLoading] = useState(true); 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 [rows, setRows] = useState([]); const [columns, setColumns] = useState([]); useEffect(() => { if(!loading) { return; } (async () => { const fullFieldName = (fieldTableName == tableMetaData.name ? "" : `${fieldTableName}.`) + fieldMetaData.name; const formData = new FormData(); formData.append("tableName", tableMetaData.name); formData.append("fieldName", fullFieldName); formData.append("filterJSON", JSON.stringify(filter)); formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 300 * 1000); if(orderBy) { formData.append("orderBy", orderBy); } const processResult = await qController.processRun("columnStats", formData); setStatusString(null); if (processResult instanceof QJobError) { const jobError = processResult as QJobError; setStatusString("Error fetching column stats: " + jobError.error); setLoading(false); } else { // todo - job running! const result = processResult as QJobComplete; const statFieldObjects = result.values.statsFields; if(statFieldObjects && statFieldObjects.length) { const newStatsFields = [] as QFieldMetaData[]; for(let i = 0; i(); fakeTableMetaData.fields.set(fieldMetaData.name, fieldMetaData); fakeTableMetaData.fields.set("count", new QFieldMetaData({name: "count", label: "Count", type: "INTEGER"})); fakeTableMetaData.fields.set("percent", new QFieldMetaData({name: "percent", label: "Percent", type: "DECIMAL"})); fakeTableMetaData.sections = [] as QTableSection[]; fakeTableMetaData.sections.push(new QTableSection({fieldNames: [fieldMetaData.name, "count", "percent"]})); const rows = DataGridUtils.makeRows(valueCounts, fakeTableMetaData); const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, null, null, "bySection"); columns.forEach((c) => { c.filterable = false; c.hideable = false; }) setRows(rows); setColumns(columns); setLoading(false); } })(); }, [loading]); function CustomPagination() { return ( {rows && rows.length && countDistinct && rows.length < countDistinct ? Showing the first {rows.length.toLocaleString()} of {countDistinct.toLocaleString()} values : <>} {rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length == 1 ? Showing the only value : <>} {rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length > 1 ? Showing all {rows.length.toLocaleString()} values : <>} ); } const refresh = () => { setLoading(true); setStatusString("Refreshing..."); }; const doExport = () => { let csv = `"${ValueUtils.cleanForCsv(fieldMetaData.label)}","Count"\n`; for (let i = 0; i < valueCounts.length; i++) { const fieldValue = valueCounts[i].displayValues.get(fieldMetaData.name); const count = valueCounts[i].values.get("count"); csv += `"${ValueUtils.cleanForCsv(fieldValue)}",${count}\n`; } const fileName = tableMetaData.label + " - " + fieldMetaData.label + " Column Stats " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv"; HtmlUtils.download(fileName, csv); }; function Loading() { return ( ); } const handleSortChange = (gridSort: GridSortModel) => { if (gridSort && gridSort.length > 0) { console.log("Sort: ", gridSort[0]); setOrderBy(`${gridSort[0].field}.${gridSort[0].sort}`); refresh(); } }; return ( Column Statistics for {fieldMetaData.label} {statusString ?? <> } (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")} sortingMode={"server"} onSortModelChange={handleSortChange} sortingOrder={["desc", "asc"]} pagination={true} paginationMode={"server"} initialState={{ sorting: { sortModel: [ { field: "count", sort: "desc", }, ], }, }} /> { statsFields && statsFields.map((field) => (
{field.label}:
{ValueUtils.getValueForDisplay(field, statsRecord?.values.get(field.name), statsRecord?.displayValues.get(field.name))}
)) }
); } export default ColumnStats;