mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Checkpoint on report and export changes, possible value translating
This commit is contained in:
@ -92,6 +92,12 @@ public class RecordAutomationStatusUpdater
|
|||||||
{
|
{
|
||||||
for(QRecord record : records)
|
for(QRecord record : records)
|
||||||
{
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - seems like there's some case here, where if an order was in PENDING_INSERT, but then some other job updated the record, that we'd //
|
||||||
|
// lose that pending status, which would be a Bad Thing™... //
|
||||||
|
// problem is - we may not have the full record in here, so we can't necessarily check the record to see what status it's currently in... //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
record.setValue(automationDetails.getStatusTracking().getFieldName(), automationStatus.getId());
|
record.setValue(automationDetails.getStatusTracking().getFieldName(), automationStatus.getId());
|
||||||
// todo - another field - for the automation timestamp??
|
// todo - another field - for the automation timestamp??
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Subclass of RecordPipe, which uses a buffer in the addRecord method, to avoid
|
||||||
|
** sending single-records at a time through postRecordActions and to consumers.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class BufferedRecordPipe extends RecordPipe
|
||||||
|
{
|
||||||
|
private List<QRecord> buffer = new ArrayList<>();
|
||||||
|
private Integer bufferSize = 100;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor - uses default buffer size
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public BufferedRecordPipe()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor - customize buffer size.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public BufferedRecordPipe(Integer bufferSize)
|
||||||
|
{
|
||||||
|
this.bufferSize = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void addRecord(QRecord record)
|
||||||
|
{
|
||||||
|
buffer.add(record);
|
||||||
|
if(buffer.size() >= bufferSize)
|
||||||
|
{
|
||||||
|
addRecords(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void finalFlush()
|
||||||
|
{
|
||||||
|
if(!buffer.isEmpty())
|
||||||
|
{
|
||||||
|
addRecords(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -247,14 +247,6 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
for(QFieldMetaData field : fields)
|
for(QFieldMetaData field : fields)
|
||||||
{
|
{
|
||||||
Serializable value = qRecord.getValue(field.getName());
|
Serializable value = qRecord.getValue(field.getName());
|
||||||
if(field.getPossibleValueSourceName() != null)
|
|
||||||
{
|
|
||||||
String displayValue = qRecord.getDisplayValue(field.getName());
|
|
||||||
if(displayValue != null)
|
|
||||||
{
|
|
||||||
value = displayValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(value != null)
|
if(value != null)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
@ -43,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
@ -147,17 +148,18 @@ public class ExportAction
|
|||||||
//////////////////////////
|
//////////////////////////
|
||||||
// set up a query input //
|
// set up a query input //
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
QueryInterface queryInterface = backendModule.getQueryInterface();
|
QueryAction queryAction = new QueryAction();
|
||||||
QueryInput queryInput = new QueryInput(exportInput.getInstance());
|
QueryInput queryInput = new QueryInput(exportInput.getInstance());
|
||||||
queryInput.setSession(exportInput.getSession());
|
queryInput.setSession(exportInput.getSession());
|
||||||
queryInput.setTableName(exportInput.getTableName());
|
queryInput.setTableName(exportInput.getTableName());
|
||||||
queryInput.setFilter(exportInput.getQueryFilter());
|
queryInput.setFilter(exportInput.getQueryFilter());
|
||||||
queryInput.setLimit(exportInput.getLimit());
|
queryInput.setLimit(exportInput.getLimit());
|
||||||
|
queryInput.setShouldTranslatePossibleValues(true);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// tell this query that it needs to put its output into a pipe //
|
// tell this query that it needs to put its output into a pipe //
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
RecordPipe recordPipe = new RecordPipe();
|
RecordPipe recordPipe = new BufferedRecordPipe(500);
|
||||||
queryInput.setRecordPipe(recordPipe);
|
queryInput.setRecordPipe(recordPipe);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -165,13 +167,14 @@ public class ExportAction
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
ReportFormat reportFormat = exportInput.getReportFormat();
|
ReportFormat reportFormat = exportInput.getReportFormat();
|
||||||
ExportStreamerInterface reportStreamer = reportFormat.newReportStreamer();
|
ExportStreamerInterface reportStreamer = reportFormat.newReportStreamer();
|
||||||
reportStreamer.start(exportInput, getFields(exportInput), "Sheet 1");
|
List<QFieldMetaData> fields = getFields(exportInput);
|
||||||
|
reportStreamer.start(exportInput, fields, "Sheet 1");
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
// run the query action as an async job //
|
// run the query action as an async job //
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
AsyncJobManager asyncJobManager = new AsyncJobManager();
|
AsyncJobManager asyncJobManager = new AsyncJobManager();
|
||||||
String queryJobUUID = asyncJobManager.startJob("ReportAction>QueryAction", (status) -> (queryInterface.execute(queryInput)));
|
String queryJobUUID = asyncJobManager.startJob("ReportAction>QueryAction", (status) -> (queryAction.execute(queryInput)));
|
||||||
LOG.info("Started query job [" + queryJobUUID + "] for report");
|
LOG.info("Started query job [" + queryJobUUID + "] for report");
|
||||||
|
|
||||||
AsyncJobState queryJobState = AsyncJobState.RUNNING;
|
AsyncJobState queryJobState = AsyncJobState.RUNNING;
|
||||||
@ -209,7 +212,7 @@ public class ExportAction
|
|||||||
nextSleepMillis = INIT_SLEEP_MS;
|
nextSleepMillis = INIT_SLEEP_MS;
|
||||||
|
|
||||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||||
reportStreamer.addRecords(records);
|
processRecords(reportStreamer, fields, records);
|
||||||
recordCount += records.size();
|
recordCount += records.size();
|
||||||
|
|
||||||
LOG.info(countFromPreExecute != null
|
LOG.info(countFromPreExecute != null
|
||||||
@ -238,7 +241,7 @@ public class ExportAction
|
|||||||
// send the final records to the report streamer //
|
// send the final records to the report streamer //
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||||
reportStreamer.addRecords(records);
|
processRecords(reportStreamer, fields, records);
|
||||||
recordCount += records.size();
|
recordCount += records.size();
|
||||||
|
|
||||||
long reportEndTime = System.currentTimeMillis();
|
long reportEndTime = System.currentTimeMillis();
|
||||||
@ -269,20 +272,59 @@ public class ExportAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void processRecords(ExportStreamerInterface reportStreamer, List<QFieldMetaData> fields, List<QRecord> records) throws QReportingException
|
||||||
|
{
|
||||||
|
for(QFieldMetaData field : fields)
|
||||||
|
{
|
||||||
|
if(field.getName().endsWith(":possibleValueLabel"))
|
||||||
|
{
|
||||||
|
String effectiveFieldName = field.getName().replace(":possibleValueLabel", "");
|
||||||
|
for(QRecord record : records)
|
||||||
|
{
|
||||||
|
String displayValue = record.getDisplayValue(effectiveFieldName);
|
||||||
|
record.setValue(field.getName(), displayValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportStreamer.addRecords(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private List<QFieldMetaData> getFields(ExportInput exportInput)
|
private List<QFieldMetaData> getFields(ExportInput exportInput)
|
||||||
{
|
{
|
||||||
|
List<QFieldMetaData> fieldList;
|
||||||
QTableMetaData table = exportInput.getTable();
|
QTableMetaData table = exportInput.getTable();
|
||||||
if(exportInput.getFieldNames() != null)
|
if(exportInput.getFieldNames() != null)
|
||||||
{
|
{
|
||||||
return (exportInput.getFieldNames().stream().map(table::getField).toList());
|
fieldList = exportInput.getFieldNames().stream().map(table::getField).toList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (new ArrayList<>(table.getFields().values()));
|
fieldList = new ArrayList<>(table.getFields().values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// add fields for possible value labels //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
List<QFieldMetaData> returnList = new ArrayList<>();
|
||||||
|
for(QFieldMetaData field : fieldList)
|
||||||
|
{
|
||||||
|
returnList.add(field);
|
||||||
|
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
||||||
|
{
|
||||||
|
returnList.add(new QFieldMetaData(field.getName() + ":possibleValueLabel", QFieldType.STRING).withLabel(field.getLabel() + " Name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (returnList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,11 +29,13 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.DataSourceQueryInputCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer;
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
@ -68,6 +70,8 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
|||||||
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
|
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
|
||||||
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
|
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
|
||||||
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -84,6 +88,8 @@ import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class GenerateReportAction
|
public class GenerateReportAction
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(GenerateReportAction.class);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// summaryAggregates and varianceAggregates are multi-level maps, ala: //
|
// summaryAggregates and varianceAggregates are multi-level maps, ala: //
|
||||||
// viewName > SummaryKey > fieldName > Aggregates //
|
// viewName > SummaryKey > fieldName > Aggregates //
|
||||||
@ -214,10 +220,7 @@ public class GenerateReportAction
|
|||||||
|
|
||||||
JoinsContext joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins());
|
JoinsContext joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins());
|
||||||
|
|
||||||
List<QFieldMetaData> fields;
|
List<QFieldMetaData> fields = new ArrayList<>();
|
||||||
if(CollectionUtils.nullSafeHasContents(reportView.getColumns()))
|
|
||||||
{
|
|
||||||
fields = new ArrayList<>();
|
|
||||||
for(QReportField column : reportView.getColumns())
|
for(QReportField column : reportView.getColumns())
|
||||||
{
|
{
|
||||||
if(column.getIsVirtual())
|
if(column.getIsVirtual())
|
||||||
@ -226,10 +229,11 @@ public class GenerateReportAction
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(column.getName());
|
String effectiveFieldName = Objects.requireNonNullElse(column.getSourceFieldName(), column.getName());
|
||||||
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(effectiveFieldName);
|
||||||
if(fieldAndTableNameOrAlias.field() == null)
|
if(fieldAndTableNameOrAlias.field() == null)
|
||||||
{
|
{
|
||||||
throw new QReportingException("Could not find field named [" + column.getName() + "] on table [" + table.getName() + "]");
|
throw new QReportingException("Could not find field named [" + effectiveFieldName + "] on table [" + table.getName() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
QFieldMetaData field = fieldAndTableNameOrAlias.field().clone();
|
QFieldMetaData field = fieldAndTableNameOrAlias.field().clone();
|
||||||
@ -241,11 +245,7 @@ public class GenerateReportAction
|
|||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fields = new ArrayList<>(table.getFields().values());
|
|
||||||
}
|
|
||||||
reportStreamer.setDisplayFormats(getDisplayFormatMap(fields));
|
reportStreamer.setDisplayFormats(getDisplayFormatMap(fields));
|
||||||
reportStreamer.start(exportInput, fields, reportView.getLabel());
|
reportStreamer.start(exportInput, fields, reportView.getLabel());
|
||||||
}
|
}
|
||||||
@ -286,7 +286,7 @@ public class GenerateReportAction
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// run a record pipe loop, over the query for this data source //
|
// run a record pipe loop, over the query for this data source //
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
RecordPipe recordPipe = new RecordPipe();
|
RecordPipe recordPipe = new BufferedRecordPipe(1000);
|
||||||
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
||||||
{
|
{
|
||||||
if(dataSource.getSourceTable() != null)
|
if(dataSource.getSourceTable() != null)
|
||||||
@ -301,6 +301,13 @@ public class GenerateReportAction
|
|||||||
queryInput.setFilter(queryFilter);
|
queryInput.setFilter(queryFilter);
|
||||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
||||||
|
|
||||||
|
if(dataSource.getQueryInputCustomizer() != null)
|
||||||
|
{
|
||||||
|
DataSourceQueryInputCustomizer queryInputCustomizer = QCodeLoader.getAdHoc(DataSourceQueryInputCustomizer.class, dataSource.getQueryInputCustomizer());
|
||||||
|
queryInput = queryInputCustomizer.run(reportInput, queryInput);
|
||||||
|
}
|
||||||
|
|
||||||
return (new QueryAction().execute(queryInput));
|
return (new QueryAction().execute(queryInput));
|
||||||
}
|
}
|
||||||
else if(dataSource.getStaticDataSupplier() != null)
|
else if(dataSource.getStaticDataSupplier() != null)
|
||||||
@ -369,7 +376,8 @@ public class GenerateReportAction
|
|||||||
for(Serializable value : criterion.getValues())
|
for(Serializable value : criterion.getValues())
|
||||||
{
|
{
|
||||||
String valueAsString = ValueUtils.getValueAsString(value);
|
String valueAsString = ValueUtils.getValueAsString(value);
|
||||||
Serializable interpretedValue = variableInterpreter.interpret(valueAsString);
|
// Serializable interpretedValue = variableInterpreter.interpret(valueAsString);
|
||||||
|
Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString);
|
||||||
newValues.add(interpretedValue);
|
newValues.add(interpretedValue);
|
||||||
}
|
}
|
||||||
criterion.setValues(newValues);
|
criterion.setValues(newValues);
|
||||||
@ -391,6 +399,22 @@ public class GenerateReportAction
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
if(tableView != null)
|
if(tableView != null)
|
||||||
{
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QReportField column : tableView.getColumns())
|
||||||
|
{
|
||||||
|
if(column.getShowPossibleValueLabel())
|
||||||
|
{
|
||||||
|
String effectiveFieldName = Objects.requireNonNullElse(column.getSourceFieldName(), column.getName());
|
||||||
|
for(QRecord record : records)
|
||||||
|
{
|
||||||
|
String displayValue = record.getDisplayValue(effectiveFieldName);
|
||||||
|
record.setValue(column.getName(), displayValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reportStreamer.addRecords(records);
|
reportStreamer.addRecords(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,14 +24,18 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -49,6 +53,7 @@ public class JsonExportStreamer implements ExportStreamerInterface
|
|||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
|
|
||||||
private boolean needComma = false;
|
private boolean needComma = false;
|
||||||
|
private boolean prettyPrint = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -74,7 +79,7 @@ public class JsonExportStreamer implements ExportStreamerInterface
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputStream.write("[".getBytes(StandardCharsets.UTF_8));
|
outputStream.write('[');
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(IOException e)
|
||||||
{
|
{
|
||||||
@ -109,11 +114,25 @@ public class JsonExportStreamer implements ExportStreamerInterface
|
|||||||
{
|
{
|
||||||
if(needComma)
|
if(needComma)
|
||||||
{
|
{
|
||||||
outputStream.write(",".getBytes(StandardCharsets.UTF_8));
|
outputStream.write(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Serializable> mapForJson = new LinkedHashMap<>();
|
||||||
|
for(QFieldMetaData field : fields)
|
||||||
|
{
|
||||||
|
String labelForJson = StringUtils.lcFirst(field.getLabel().replace(" ", ""));
|
||||||
|
mapForJson.put(labelForJson, qRecord.getValue(field.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = prettyPrint ? JsonUtils.toPrettyJson(mapForJson) : JsonUtils.toJson(mapForJson);
|
||||||
|
|
||||||
|
if(prettyPrint)
|
||||||
|
{
|
||||||
|
outputStream.write('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
String json = JsonUtils.toJson(qRecord);
|
|
||||||
outputStream.write(json.getBytes(StandardCharsets.UTF_8));
|
outputStream.write(json.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
outputStream.flush(); // todo - less often?
|
outputStream.flush(); // todo - less often?
|
||||||
needComma = true;
|
needComma = true;
|
||||||
}
|
}
|
||||||
@ -144,7 +163,11 @@ public class JsonExportStreamer implements ExportStreamerInterface
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputStream.write("]".getBytes(StandardCharsets.UTF_8));
|
if(prettyPrint)
|
||||||
|
{
|
||||||
|
outputStream.write('\n');
|
||||||
|
}
|
||||||
|
outputStream.write(']');
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(IOException e)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Interface for customizer on a QReportDataSource's query.
|
||||||
|
**
|
||||||
|
** Useful, for example, to look at what input field values were given, and change
|
||||||
|
** the query filter (e.g., conditional criteria), or issue an error based on the
|
||||||
|
** combination of input fields given.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface DataSourceQueryInputCustomizer
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
QueryInput run(ReportInput reportInput, QueryInput queryInput) throws QException;
|
||||||
|
|
||||||
|
}
|
@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
@ -36,6 +37,8 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -44,6 +47,8 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryAction
|
public class QueryAction
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(QueryAction.class);
|
||||||
|
|
||||||
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
||||||
|
|
||||||
private QueryInput queryInput;
|
private QueryInput queryInput;
|
||||||
@ -72,6 +77,11 @@ public class QueryAction
|
|||||||
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
||||||
// todo post-customization - can do whatever w/ the result if you want
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
|
|
||||||
|
if(queryInput.getRecordPipe() instanceof BufferedRecordPipe bufferedRecordPipe)
|
||||||
|
{
|
||||||
|
bufferedRecordPipe.finalFlush();
|
||||||
|
}
|
||||||
|
|
||||||
if(queryInput.getRecordPipe() == null)
|
if(queryInput.getRecordPipe() == null)
|
||||||
{
|
{
|
||||||
postRecordActions(queryOutput.getRecords());
|
postRecordActions(queryOutput.getRecords());
|
||||||
@ -100,7 +110,7 @@ public class QueryAction
|
|||||||
{
|
{
|
||||||
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||||
}
|
}
|
||||||
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records);
|
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records, queryInput.getQueryJoins());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryInput.getShouldGenerateDisplayValues())
|
if(queryInput.getShouldGenerateDisplayValues())
|
||||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -38,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperat
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -50,6 +52,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -91,13 +95,24 @@ public class QPossibleValueTranslator
|
|||||||
** For a list of records, translate their possible values (populating their display values)
|
** For a list of records, translate their possible values (populating their display values)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||||
|
{
|
||||||
|
translatePossibleValuesInRecords(table, records, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** For a list of records, translate their possible values (populating their display values)
|
||||||
|
*******************************************************************************/
|
||||||
|
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins)
|
||||||
{
|
{
|
||||||
if(records == null || table == null)
|
if(records == null || table == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
primePvsCache(table, records);
|
LOG.debug("Translating possible values in [" + records.size() + "] records from the [" + table.getName() + "] table.");
|
||||||
|
primePvsCache(table, records, queryJoins);
|
||||||
|
|
||||||
for(QRecord record : records)
|
for(QRecord record : records)
|
||||||
{
|
{
|
||||||
@ -108,6 +123,42 @@ public class QPossibleValueTranslator
|
|||||||
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
|
{
|
||||||
|
if(queryJoin.getSelect())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// todo - aliases aren't be handled right //
|
||||||
|
////////////////////////////////////////////
|
||||||
|
QTableMetaData joinTable = qInstance.getTable(queryJoin.getRightTable());
|
||||||
|
for(QFieldMetaData field : joinTable.getFields().values())
|
||||||
|
{
|
||||||
|
if(field.getPossibleValueSourceName() != null)
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// avoid circling-back upon the source table //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||||
|
if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()) && table.getName().equals(possibleValueSource.getTableName()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String joinFieldName = joinTable.getName() + "." + field.getName();
|
||||||
|
record.setDisplayValue(joinFieldName, translatePossibleValue(field, record.getValue(joinFieldName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error translating join table possible values", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,8 +295,7 @@ public class QPossibleValueTranslator
|
|||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
// look for cached value - if it's missing, call the primer //
|
// look for cached value - if it's missing, call the primer //
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
Map<Serializable, String> cacheForPvs = possibleValueCache.computeIfAbsent(possibleValueSource.getName(), x -> new HashMap<>());
|
||||||
Map<Serializable, String> cacheForPvs = possibleValueCache.get(possibleValueSource.getName());
|
|
||||||
if(!cacheForPvs.containsKey(value))
|
if(!cacheForPvs.containsKey(value))
|
||||||
{
|
{
|
||||||
primePvsCache(possibleValueSource.getTableName(), List.of(possibleValueSource), List.of(value));
|
primePvsCache(possibleValueSource.getTableName(), List.of(possibleValueSource), List.of(value));
|
||||||
@ -329,21 +379,29 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
||||||
**
|
|
||||||
** @param table the table that the records are from
|
** @param table the table that the records are from
|
||||||
** @param records the records that have the possible value id's (e.g., foreign keys)
|
** @param records the records that have the possible value id's (e.g., foreign keys)
|
||||||
|
* @param queryJoins
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void primePvsCache(QTableMetaData table, List<QRecord> records)
|
void primePvsCache(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins)
|
||||||
{
|
{
|
||||||
ListingHash<String, QFieldMetaData> fieldsByPvsTable = new ListingHash<>();
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this is a map of String(tableName - the PVS table) to Pair(String (either "" for main table in a query, or join-table + "."), field (from the table being selected from)) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
ListingHash<String, Pair<String, QFieldMetaData>> fieldsByPvsTable = new ListingHash<>();
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this is a map of String(tableName - the PVS table) to PossibleValueSource objects //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
|
||||||
|
primePvsCacheTableListingHashLoader(table, fieldsByPvsTable, pvsesByTable, "");
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
{
|
{
|
||||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
if(queryJoin.getSelect())
|
||||||
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
|
||||||
{
|
{
|
||||||
fieldsByPvsTable.add(possibleValueSource.getTableName(), field);
|
// todo - aliases probably not handled right
|
||||||
pvsesByTable.add(possibleValueSource.getTableName(), possibleValueSource);
|
primePvsCacheTableListingHashLoader(qInstance.getTable(queryJoin.getRightTable()), fieldsByPvsTable, pvsesByTable, queryJoin.getRightTable() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,16 +410,24 @@ public class QPossibleValueTranslator
|
|||||||
Set<Serializable> values = new HashSet<>();
|
Set<Serializable> values = new HashSet<>();
|
||||||
for(QRecord record : records)
|
for(QRecord record : records)
|
||||||
{
|
{
|
||||||
for(QFieldMetaData field : fieldsByPvsTable.get(tableName))
|
for(Pair<String, QFieldMetaData> fieldPair : fieldsByPvsTable.get(tableName))
|
||||||
{
|
{
|
||||||
Serializable fieldValue = record.getValue(field.getName());
|
String fieldName = fieldPair.getA() + fieldPair.getB().getName();
|
||||||
|
Serializable fieldValue = record.getValue(fieldName);
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// ignore null and empty-string values //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
if(!StringUtils.hasContent(ValueUtils.getValueAsString(fieldValue)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
// check if value is already cached //
|
// check if value is already cached //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
QPossibleValueSource possibleValueSource = pvsesByTable.get(tableName).get(0);
|
QPossibleValueSource possibleValueSource = pvsesByTable.get(tableName).get(0);
|
||||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
Map<Serializable, String> cacheForPvs = possibleValueCache.computeIfAbsent(possibleValueSource.getName(), x -> new HashMap<>());
|
||||||
Map<Serializable, String> cacheForPvs = possibleValueCache.get(possibleValueSource.getName());
|
|
||||||
|
|
||||||
if(!cacheForPvs.containsKey(fieldValue))
|
if(!cacheForPvs.containsKey(fieldValue))
|
||||||
{
|
{
|
||||||
@ -379,6 +445,28 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Helper for the primePvsCache method
|
||||||
|
*******************************************************************************/
|
||||||
|
private void primePvsCacheTableListingHashLoader(QTableMetaData table, ListingHash<String, Pair<String, QFieldMetaData>> fieldsByPvsTable, ListingHash<String, QPossibleValueSource> pvsesByTable, String fieldNamePrefix)
|
||||||
|
{
|
||||||
|
for(QFieldMetaData field : table.getFields().values())
|
||||||
|
{
|
||||||
|
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||||
|
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||||
|
{
|
||||||
|
fieldsByPvsTable.add(possibleValueSource.getTableName(), Pair.of(fieldNamePrefix, field));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - optimization we can put the same PVS in this listing hash multiple times... either check for dupes, or change to a set, or something smarter. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
pvsesByTable.add(possibleValueSource.getTableName(), possibleValueSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** For a given table, and a list of pkey-values in that table, AND a list of
|
** For a given table, and a list of pkey-values in that table, AND a list of
|
||||||
** possible value sources based on that table (maybe usually 1, but could be more,
|
** possible value sources based on that table (maybe usually 1, but could be more,
|
||||||
@ -408,10 +496,12 @@ public class QPossibleValueTranslator
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(notTooDeep())
|
if(notTooDeep())
|
||||||
{
|
{
|
||||||
queryInput.setShouldTranslatePossibleValues(true);
|
// todo not commit...
|
||||||
|
// queryInput.setShouldTranslatePossibleValues(true);
|
||||||
queryInput.setShouldGenerateDisplayValues(true);
|
queryInput.setShouldGenerateDisplayValues(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG.debug("Priming PVS cache for [" + page.size() + "] ids from [" + tableName + "] table.");
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
@ -58,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||||
@ -984,7 +986,7 @@ public class QInstanceValidator
|
|||||||
{
|
{
|
||||||
if(dataSource.getQueryFilter() != null)
|
if(dataSource.getQueryFilter() != null)
|
||||||
{
|
{
|
||||||
validateQueryFilter("In " + dataSourceErrorPrefix + "query filter - ", qInstance.getTable(dataSource.getSourceTable()), dataSource.getQueryFilter());
|
validateQueryFilter(qInstance, "In " + dataSourceErrorPrefix + "query filter - ", qInstance.getTable(dataSource.getSourceTable()), dataSource.getQueryFilter(), dataSource.getQueryJoins());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -999,9 +1001,9 @@ public class QInstanceValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////
|
//////////////////////////////////
|
||||||
// validate dataSources in the report //
|
// validate views in the report //
|
||||||
////////////////////////////////////////
|
//////////////////////////////////
|
||||||
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getViews()), "At least 1 view must be defined in report " + reportName + "."))
|
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getViews()), "At least 1 view must be defined in report " + reportName + "."))
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@ -1015,19 +1017,30 @@ public class QInstanceValidator
|
|||||||
usedViewNames.add(view.getName());
|
usedViewNames.add(view.getName());
|
||||||
|
|
||||||
String viewErrorPrefix = "Report " + reportName + " view " + view.getName() + " ";
|
String viewErrorPrefix = "Report " + reportName + " view " + view.getName() + " ";
|
||||||
assertCondition(view.getType() != null, viewErrorPrefix + " is missing its type.");
|
assertCondition(view.getType() != null, viewErrorPrefix + "is missing its type.");
|
||||||
if(assertCondition(StringUtils.hasContent(view.getDataSourceName()), viewErrorPrefix + " is missing a dataSourceName"))
|
if(assertCondition(StringUtils.hasContent(view.getDataSourceName()), viewErrorPrefix + "is missing a dataSourceName"))
|
||||||
{
|
{
|
||||||
assertCondition(usedDataSourceNames.contains(view.getDataSourceName()), viewErrorPrefix + " has an unrecognized dataSourceName: " + view.getDataSourceName());
|
assertCondition(usedDataSourceNames.contains(view.getDataSourceName()), viewErrorPrefix + "has an unrecognized dataSourceName: " + view.getDataSourceName());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(StringUtils.hasContent(view.getVarianceDataSourceName()))
|
if(StringUtils.hasContent(view.getVarianceDataSourceName()))
|
||||||
{
|
{
|
||||||
assertCondition(usedDataSourceNames.contains(view.getVarianceDataSourceName()), viewErrorPrefix + " has an unrecognized varianceDataSourceName: " + view.getVarianceDataSourceName());
|
assertCondition(usedDataSourceNames.contains(view.getVarianceDataSourceName()), viewErrorPrefix + "has an unrecognized varianceDataSourceName: " + view.getVarianceDataSourceName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// actually, this is okay if there's a customizer, so...
|
boolean hasColumns = CollectionUtils.nullSafeHasContents(view.getColumns());
|
||||||
assertCondition(CollectionUtils.nullSafeHasContents(view.getColumns()), viewErrorPrefix + " does not have any columns.");
|
boolean hasViewCustomizer = view.getViewCustomizer() != null;
|
||||||
|
assertCondition(hasColumns || hasViewCustomizer, viewErrorPrefix + "does not have any columns or a view customizer.");
|
||||||
|
|
||||||
|
Set<String> usedColumnNames = new HashSet<>();
|
||||||
|
for(QReportField column : CollectionUtils.nonNullList(view.getColumns()))
|
||||||
|
{
|
||||||
|
assertCondition(StringUtils.hasContent(column.getName()), viewErrorPrefix + "has a column with no name.");
|
||||||
|
assertCondition(!usedColumnNames.contains(column.getName()), viewErrorPrefix + "has multiple columns named: " + column.getName());
|
||||||
|
usedColumnNames.add(column.getName());
|
||||||
|
|
||||||
|
// todo - is field name valid?
|
||||||
|
}
|
||||||
|
|
||||||
// todo - all these too...
|
// todo - all these too...
|
||||||
// view.getPivotFields();
|
// view.getPivotFields();
|
||||||
@ -1047,34 +1060,74 @@ public class QInstanceValidator
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void validateQueryFilter(String context, QTableMetaData table, QQueryFilter queryFilter)
|
private void validateQueryFilter(QInstance qInstance, String context, QTableMetaData table, QQueryFilter queryFilter, List<QueryJoin> queryJoins)
|
||||||
{
|
{
|
||||||
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
|
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
|
||||||
{
|
{
|
||||||
if(assertCondition(StringUtils.hasContent(criterion.getFieldName()), context + "Missing fieldName for a criteria"))
|
String fieldName = criterion.getFieldName();
|
||||||
|
if(assertCondition(StringUtils.hasContent(fieldName), context + "Missing fieldName for a criteria"))
|
||||||
{
|
{
|
||||||
assertNoException(() -> table.getField(criterion.getFieldName()), context + "Criteria fieldName " + criterion.getFieldName() + " is not a field in this table.");
|
assertCondition(findField(qInstance, table, queryJoins, fieldName), context + "Criteria fieldName " + fieldName + " is not a field in this table (or in any given joins).");
|
||||||
}
|
}
|
||||||
assertCondition(criterion.getOperator() != null, context + "Missing operator for a criteria on fieldName " + criterion.getFieldName());
|
assertCondition(criterion.getOperator() != null, context + "Missing operator for a criteria on fieldName " + fieldName);
|
||||||
assertCondition(criterion.getValues() != null, context + "Missing values for a criteria on fieldName " + criterion.getFieldName()); // todo - what about ops w/ no value (BLANK)
|
assertCondition(criterion.getValues() != null, context + "Missing values for a criteria on fieldName " + fieldName); // todo - what about ops w/ no value (BLANK)
|
||||||
}
|
}
|
||||||
|
|
||||||
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(queryFilter.getOrderBys()))
|
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(queryFilter.getOrderBys()))
|
||||||
{
|
{
|
||||||
if(assertCondition(StringUtils.hasContent(orderBy.getFieldName()), context + "Missing fieldName for an orderBy"))
|
if(assertCondition(StringUtils.hasContent(orderBy.getFieldName()), context + "Missing fieldName for an orderBy"))
|
||||||
{
|
{
|
||||||
assertNoException(() -> table.getField(orderBy.getFieldName()), context + "OrderBy fieldName " + orderBy.getFieldName() + " is not a field in this table.");
|
assertCondition(findField(qInstance, table, queryJoins, orderBy.getFieldName()), context + "OrderBy fieldName " + orderBy.getFieldName() + " is not a field in this table (or in any given joins).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(queryFilter.getSubFilters()))
|
for(QQueryFilter subFilter : CollectionUtils.nonNullList(queryFilter.getSubFilters()))
|
||||||
{
|
{
|
||||||
validateQueryFilter(context, table, subFilter);
|
validateQueryFilter(qInstance, context, table, subFilter, queryJoins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Look for a field name in either a table, or the tables referenced in a list of query joins.
|
||||||
|
*******************************************************************************/
|
||||||
|
private static boolean findField(QInstance qInstance, QTableMetaData table, List<QueryJoin> queryJoins, String fieldName)
|
||||||
|
{
|
||||||
|
boolean foundField = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
table.getField(fieldName);
|
||||||
|
foundField = true;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
if(fieldName.contains("."))
|
||||||
|
{
|
||||||
|
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = qInstance.getTable(queryJoin.getRightTable());
|
||||||
|
if(joinTable != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
joinTable.getField(fieldNameAfterDot);
|
||||||
|
foundField = true;
|
||||||
|
}
|
||||||
|
catch(Exception e2)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -42,6 +42,7 @@ public class QReportDataSource
|
|||||||
private List<QueryJoin> queryJoins = null;
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
|
private QCodeReference queryInputCustomizer;
|
||||||
private QCodeReference staticDataSupplier;
|
private QCodeReference staticDataSupplier;
|
||||||
|
|
||||||
|
|
||||||
@ -230,4 +231,38 @@ public class QReportDataSource
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryInputCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getQueryInputCustomizer()
|
||||||
|
{
|
||||||
|
return queryInputCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryInputCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryInputCustomizer(QCodeReference queryInputCustomizer)
|
||||||
|
{
|
||||||
|
this.queryInputCustomizer = queryInputCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryInputCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withQueryInputCustomizer(QCodeReference queryInputCustomizer)
|
||||||
|
{
|
||||||
|
this.queryInputCustomizer = queryInputCustomizer;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,9 @@ public class QReportField
|
|||||||
|
|
||||||
private boolean isVirtual = false;
|
private boolean isVirtual = false;
|
||||||
|
|
||||||
|
private boolean showPossibleValueLabel = false;
|
||||||
|
private String sourceFieldName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -281,4 +284,73 @@ public class QReportField
|
|||||||
this.isVirtual = isVirtual;
|
this.isVirtual = isVirtual;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for sourceFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSourceFieldName()
|
||||||
|
{
|
||||||
|
return sourceFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for sourceFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSourceFieldName(String sourceFieldName)
|
||||||
|
{
|
||||||
|
this.sourceFieldName = sourceFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for sourceFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField withSourceFieldName(String sourceFieldName)
|
||||||
|
{
|
||||||
|
this.sourceFieldName = sourceFieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for showPossibleValueLabel
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getShowPossibleValueLabel()
|
||||||
|
{
|
||||||
|
return showPossibleValueLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for showPossibleValueLabel
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setShowPossibleValueLabel(boolean showPossibleValueLabel)
|
||||||
|
{
|
||||||
|
this.showPossibleValueLabel = showPossibleValueLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for showPossibleValueLabel
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField withShowPossibleValueLabel(boolean showPossibleValueLabel)
|
||||||
|
{
|
||||||
|
this.showPossibleValueLabel = showPossibleValueLabel;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -361,4 +361,44 @@ public class StringUtils
|
|||||||
return (size != null && size.equals(1) ? ifOne : ifNotOne);
|
return (size != null && size.equals(1) ? ifOne : ifNotOne);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Lowercase the first char of a string.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String lcFirst(String s)
|
||||||
|
{
|
||||||
|
if(s == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s.length() <= 1)
|
||||||
|
{
|
||||||
|
return (s.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s.substring(0, 1).toLowerCase() + s.substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Uppercase the first char of a string.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static String ucFirst(String s)
|
||||||
|
{
|
||||||
|
if(s == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s.length() <= 1)
|
||||||
|
{
|
||||||
|
return (s.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s.substring(0, 1).toUpperCase() + s.substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -188,7 +188,7 @@ public class QPossibleValueTranslatorTest
|
|||||||
);
|
);
|
||||||
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||||
MemoryRecordStore.resetStatistics();
|
MemoryRecordStore.resetStatistics();
|
||||||
possibleValueTranslator.primePvsCache(personTable, personRecords);
|
possibleValueTranslator.primePvsCache(personTable, personRecords, null); // todo - test non-null queryJoins
|
||||||
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||||
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
||||||
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
||||||
|
@ -55,6 +55,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
@ -1436,6 +1437,69 @@ class QInstanceValidatorTest
|
|||||||
dataSource.setStaticDataSupplier(new QCodeReference(ArrayList.class, null));
|
dataSource.setStaticDataSupplier(new QCodeReference(ArrayList.class, null));
|
||||||
},
|
},
|
||||||
"is not of the expected type");
|
"is not of the expected type");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testReportViewBasics()
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).setViews(null),
|
||||||
|
"At least 1 view must be defined in report");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).setViews(new ArrayList<>()),
|
||||||
|
"At least 1 view must be defined in report");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// meh, enricher sets a default name, so, can't easily catch this one. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setName(null),
|
||||||
|
// "Missing name for a view");
|
||||||
|
// assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setName(""),
|
||||||
|
// "Missing name for a view");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setType(null),
|
||||||
|
"missing its type");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// meh, enricher sets a default name, so, can't easily catch this one. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setDataSourceName(null),
|
||||||
|
// "missing a dataSourceName");
|
||||||
|
// assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setDataSourceName(""),
|
||||||
|
// "missing a dataSourceName");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setDataSourceName("notADataSource"),
|
||||||
|
"has an unrecognized dataSourceName");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testReportViewColumns()
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).setColumns(null),
|
||||||
|
"does not have any columns or a view customizer");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).getColumns().get(0).setName(null),
|
||||||
|
"has a column with no name");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).getColumns().get(0).setName(""),
|
||||||
|
"has a column with no name");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) ->
|
||||||
|
{
|
||||||
|
List<QReportField> columns = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getViews().get(0).getColumns();
|
||||||
|
columns.get(0).setName("id");
|
||||||
|
columns.get(1).setName("id");
|
||||||
|
},
|
||||||
|
"has multiple columns named: id");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ class StringUtilsTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -246,4 +247,41 @@ class StringUtilsTest
|
|||||||
assertEquals("", StringUtils.plural(1, "", "es"));
|
assertEquals("", StringUtils.plural(1, "", "es"));
|
||||||
assertEquals("es", StringUtils.plural(2, "", "es"));
|
assertEquals("es", StringUtils.plural(2, "", "es"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testLcFirst()
|
||||||
|
{
|
||||||
|
assertNull(StringUtils.lcFirst(null));
|
||||||
|
assertEquals("", StringUtils.lcFirst(""));
|
||||||
|
assertEquals(" ", StringUtils.lcFirst(" "));
|
||||||
|
assertEquals("a", StringUtils.lcFirst("A"));
|
||||||
|
assertEquals("1", StringUtils.lcFirst("1"));
|
||||||
|
assertEquals("a", StringUtils.lcFirst("a"));
|
||||||
|
assertEquals("aB", StringUtils.lcFirst("AB"));
|
||||||
|
assertEquals("aBc", StringUtils.lcFirst("ABc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testUcFirst()
|
||||||
|
{
|
||||||
|
assertNull(StringUtils.ucFirst(null));
|
||||||
|
assertEquals("", StringUtils.ucFirst(""));
|
||||||
|
assertEquals(" ", StringUtils.ucFirst(" "));
|
||||||
|
assertEquals("A", StringUtils.ucFirst("A"));
|
||||||
|
assertEquals("1", StringUtils.ucFirst("1"));
|
||||||
|
assertEquals("A", StringUtils.ucFirst("a"));
|
||||||
|
assertEquals("Ab", StringUtils.ucFirst("ab"));
|
||||||
|
assertEquals("Abc", StringUtils.ucFirst("abc"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||||
@ -140,6 +141,7 @@ public class TestUtils
|
|||||||
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
||||||
public static final String TABLE_NAME_BASEPULL = "basepullTest";
|
public static final String TABLE_NAME_BASEPULL = "basepullTest";
|
||||||
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
|
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
|
||||||
|
public static final String REPORT_NAME_PERSON_JOIN_SHAPE = "simplePersonReport";
|
||||||
|
|
||||||
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
||||||
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
||||||
@ -192,6 +194,7 @@ public class TestUtils
|
|||||||
|
|
||||||
qInstance.addReport(defineShapesPersonsReport());
|
qInstance.addReport(defineShapesPersonsReport());
|
||||||
qInstance.addProcess(defineShapesPersonReportProcess());
|
qInstance.addProcess(defineShapesPersonReportProcess());
|
||||||
|
qInstance.addReport(definePersonJoinShapeReport());
|
||||||
|
|
||||||
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
||||||
|
|
||||||
@ -1108,4 +1111,26 @@ public class TestUtils
|
|||||||
.getProcessMetaData();
|
.getProcessMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QReportMetaData definePersonJoinShapeReport()
|
||||||
|
{
|
||||||
|
return new QReportMetaData()
|
||||||
|
.withName(REPORT_NAME_PERSON_JOIN_SHAPE)
|
||||||
|
.withDataSource(
|
||||||
|
new QReportDataSource()
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
)
|
||||||
|
.withView(new QReportView()
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumns(List.of(
|
||||||
|
new QReportField("id"),
|
||||||
|
new QReportField("firstName"),
|
||||||
|
new QReportField("lastName")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -926,8 +926,7 @@ public class QJavalinImplementation
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
pipedOutputStream.write(("Error generating report: " + e.getMessage()).getBytes());
|
handleExportOrReportException(context, pipedOutputStream, e);
|
||||||
pipedOutputStream.close();
|
|
||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -953,6 +952,41 @@ public class QJavalinImplementation
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void handleExportOrReportException(Context context, PipedOutputStream pipedOutputStream, Exception e) throws IOException
|
||||||
|
{
|
||||||
|
HttpStatus.Code statusCode = HttpStatus.Code.INTERNAL_SERVER_ERROR; // 500
|
||||||
|
String message = e.getMessage();
|
||||||
|
|
||||||
|
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
|
||||||
|
if(userFacingException != null)
|
||||||
|
{
|
||||||
|
LOG.info("User-facing exception", e);
|
||||||
|
statusCode = HttpStatus.Code.BAD_REQUEST; // 400
|
||||||
|
message = userFacingException.getMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QAuthenticationException authenticationException = ExceptionUtils.findClassInRootChain(e, QAuthenticationException.class);
|
||||||
|
if(authenticationException != null)
|
||||||
|
{
|
||||||
|
statusCode = HttpStatus.Code.UNAUTHORIZED; // 401
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG.warn("Unexpected exception in javalin report or export request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.status(statusCode.getCode());
|
||||||
|
pipedOutputStream.write(("Error generating report: " + message).getBytes());
|
||||||
|
pipedOutputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user