CE-881 - Update to convert pivots to summaries for formats that don't support pivot

This commit is contained in:
2024-04-01 08:58:39 -05:00
parent 80ab0b26a0
commit a24033f572
2 changed files with 146 additions and 29 deletions

View File

@ -67,7 +67,7 @@ public class RenderSavedReportExecuteStep implements BackendStep
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report"); runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
QReportMetaData reportMetaData = new SavedReportToReportMetaDataAdapter().adapt(savedReport); QReportMetaData reportMetaData = new SavedReportToReportMetaDataAdapter().adapt(savedReport, reportFormat);
try(FileOutputStream reportOutputStream = new FileOutputStream(tmpFile)) try(FileOutputStream reportOutputStream = new FileOutputStream(tmpFile))
{ {

View File

@ -29,8 +29,13 @@ import java.util.Set;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition; import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
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.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -48,7 +53,6 @@ import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
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 com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder; import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -66,7 +70,7 @@ public class SavedReportToReportMetaDataAdapter
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public QReportMetaData adapt(SavedReport savedReport) throws QException public QReportMetaData adapt(SavedReport savedReport, ReportFormat reportFormat) throws QException
{ {
try try
{ {
@ -97,11 +101,11 @@ public class SavedReportToReportMetaDataAdapter
view.setLabel(savedReport.getLabel()); // todo eh? view.setLabel(savedReport.getLabel()); // todo eh?
view.setIncludeHeaderRow(true); view.setIncludeHeaderRow(true);
//////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// columns in the saved-report should look like a serialized version of ReportColumns object // // columns in the saved-report should look like a serialized version of ReportColumns object //
// map them to a list of QReportField objects // // map them to a list of QReportField objects //
// also keep track of what joinTables we find that we need to select // // also keep track of what joinTables we find that we need to select //
//////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
ReportColumns columnsObject = JsonUtils.toObject(savedReport.getColumnsJson(), ReportColumns.class, om -> om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); ReportColumns columnsObject = JsonUtils.toObject(savedReport.getColumnsJson(), ReportColumns.class, om -> om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
List<QReportField> reportColumns = new ArrayList<>(); List<QReportField> reportColumns = new ArrayList<>();
@ -122,9 +126,9 @@ public class SavedReportToReportMetaDataAdapter
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
// figure out the field being named by the column // // figure out the field being named by the column //
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
String fieldName = ValueUtils.getValueAsString(column.getName()); String fieldName = column.getName();
QFieldMetaData field = getField(savedReport, fieldName, qInstance, neededJoinTables, table); FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table);
if(field == null) if(fieldAndJoinTable == null)
{ {
continue; continue;
} }
@ -132,16 +136,7 @@ public class SavedReportToReportMetaDataAdapter
////////////////////////////////////////////////// //////////////////////////////////////////////////
// make a QReportField based on the table field // // make a QReportField based on the table field //
////////////////////////////////////////////////// //////////////////////////////////////////////////
QReportField reportField = new QReportField(); reportColumns.add(makeQReportField(fieldName, fieldAndJoinTable));
reportColumns.add(reportField);
reportField.setName(fieldName);
reportField.setLabel(field.getLabel());
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
{
reportField.setShowPossibleValueLabel(true);
}
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -178,17 +173,93 @@ public class SavedReportToReportMetaDataAdapter
} }
} }
/////////////////////////////////////////////// /////////////////////////////////////////
// if it's a pivot report, add that view too // // if it's a pivot report, handle that //
/////////////////////////////////////////////// /////////////////////////////////////////
if(StringUtils.hasContent(savedReport.getPivotTableJson())) if(StringUtils.hasContent(savedReport.getPivotTableJson()))
{ {
PivotTableDefinition pivotTableDefinition = JsonUtils.toObject(savedReport.getPivotTableJson(), PivotTableDefinition.class);
QReportView pivotView = new QReportView(); QReportView pivotView = new QReportView();
reportMetaData.getViews().add(pivotView); reportMetaData.getViews().add(pivotView);
pivotView.setName("pivot"); pivotView.setName("pivot");
pivotView.setType(ReportType.PIVOT); pivotView.setLabel("Pivot Table");
pivotView.setPivotTableSourceViewName(view.getName());
pivotView.setPivotTableDefinition(JsonUtils.toObject(savedReport.getPivotTableJson(), PivotTableDefinition.class)); if(reportFormat != null && reportFormat.getSupportsNativePivotTables())
{
pivotView.setType(ReportType.PIVOT);
pivotView.setPivotTableSourceViewName(view.getName());
pivotView.setPivotTableDefinition(pivotTableDefinition);
}
else
{
if(!CollectionUtils.nullSafeHasContents(pivotTableDefinition.getRows()))
{
throw (new QUserFacingException("To generate a pivot report in " + reportFormat + " format, it must have 1 or more Pivot Rows"));
}
if(CollectionUtils.nullSafeHasContents(pivotTableDefinition.getColumns()))
{
throw (new QUserFacingException("To generate a pivot report in " + reportFormat + " format, it may not have any Pivot Columns"));
}
///////////////////////
// handle pivot rows //
///////////////////////
List<String> summaryFields = new ArrayList<>();
List<QFilterOrderBy> summaryOrderByFields = new ArrayList<>();
for(PivotTableGroupBy row : pivotTableDefinition.getRows())
{
String fieldName = row.getFieldName();
FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table);
if(fieldAndJoinTable == null)
{
LOG.warn("The field for a Pivot Row wasn't found, when converting to a summary...", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName));
continue;
}
summaryFields.add(fieldName);
summaryOrderByFields.add(new QFilterOrderBy(fieldName));
}
/////////////////////////
// handle pivot values //
/////////////////////////
List<QReportField> summaryViewColumns = new ArrayList<>();
for(PivotTableValue value : pivotTableDefinition.getValues())
{
String fieldName = value.getFieldName();
FieldAndJoinTable fieldAndJoinTable = getField(savedReport, fieldName, qInstance, neededJoinTables, table);
if(fieldAndJoinTable == null)
{
LOG.warn("The field for a Pivot Value wasn't found, when converting to a summary...", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName));
continue;
}
QReportField reportField = makeQReportField(fieldName, fieldAndJoinTable);
reportField.setName(fieldName + "_" + value.getFunction().name());
reportField.setLabel(StringUtils.ucFirst(value.getFunction().name().toLowerCase()) + " Of " + reportField.getLabel());
reportField.setFormula("${pivot." + value.getFunction().name().toLowerCase() + "." + fieldName + "}");
summaryViewColumns.add(reportField);
summaryOrderByFields.add(new QFilterOrderBy(reportField.getName()));
}
pivotView.setType(ReportType.SUMMARY);
pivotView.setDataSourceName(dataSource.getName());
pivotView.setIncludeHeaderRow(true);
pivotView.setIncludeTotalRow(true);
pivotView.setColumns(summaryViewColumns);
pivotView.setSummaryFields(summaryFields);
pivotView.withOrderByFields(summaryOrderByFields);
}
////////////////////////////////////////////////////////////////////////////////////
// in case the reportFormat doesn't support multiple views, and we have a pivot - //
// then remove the data view //
////////////////////////////////////////////////////////////////////////////////////
if(reportFormat != null && !reportFormat.getSupportsMultipleViews())
{
reportMetaData.getViews().remove(0);
}
} }
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -217,7 +288,44 @@ public class SavedReportToReportMetaDataAdapter
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static QFieldMetaData getField(SavedReport savedReport, String fieldName, QInstance qInstance, Set<String> neededJoinTables, QTableMetaData table) private static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
{
QReportField reportField = new QReportField();
reportField.setName(fieldName);
if(fieldAndJoinTable.joinTable() == null)
{
////////////////////////////////////////////////////////////
// for fields from this table, just use the field's label //
////////////////////////////////////////////////////////////
reportField.setLabel(fieldAndJoinTable.field().getLabel());
}
else
{
///////////////////////////////////////////////////////////////
// for fields from join tables, use table label: field label //
///////////////////////////////////////////////////////////////
reportField.setLabel(fieldAndJoinTable.joinTable().getLabel() + ": " + fieldAndJoinTable.field().getLabel());
}
if(StringUtils.hasContent(fieldAndJoinTable.field().getPossibleValueSourceName()))
{
reportField.setShowPossibleValueLabel(true);
}
reportField.setType(fieldAndJoinTable.field().getType());
return reportField;
}
/*******************************************************************************
**
*******************************************************************************/
private static FieldAndJoinTable getField(SavedReport savedReport, String fieldName, QInstance qInstance, Set<String> neededJoinTables, QTableMetaData table)
{ {
QFieldMetaData field; QFieldMetaData field;
if(fieldName.contains(".")) if(fieldName.contains("."))
@ -240,6 +348,8 @@ public class SavedReportToReportMetaDataAdapter
LOG.warn("Saved Report has an unrecognized join field name", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName)); LOG.warn("Saved Report has an unrecognized join field name", logPair("savedReportId", savedReport.getId()), logPair("fieldName", fieldName));
return null; return null;
} }
return new FieldAndJoinTable(field, joinTable);
} }
else else
{ {
@ -255,8 +365,15 @@ public class SavedReportToReportMetaDataAdapter
} }
return null; return null;
} }
return new FieldAndJoinTable(field, null);
} }
return field;
} }
/*******************************************************************************
**
*******************************************************************************/
private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
} }