diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/ReportValuesDynamicFormWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/ReportValuesDynamicFormWidgetRenderer.java new file mode 100644 index 00000000..179bb413 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/ReportValuesDynamicFormWidgetRenderer.java @@ -0,0 +1,175 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.model.savedreports; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; +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.widgets.RenderWidgetInput; +import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.DynamicFormWidgetData; +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.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; +import org.json.JSONObject; +import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; + + +/******************************************************************************* + ** Note - exists under 2 names, for the RenderSavedReport process, and for the + ** ScheduledReport table + *******************************************************************************/ +public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRenderer +{ + private static final QLogger LOG = QLogger.getLogger(ReportValuesDynamicFormWidgetRenderer.class); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public RenderWidgetOutput render(RenderWidgetInput input) throws QException + { + try + { + List fieldList = new ArrayList<>(); + Map defaultValues = new HashMap<>(); + + ////////////////////////////////////////////////////////////////////////////// + // read params to ultimately find the query filter that has variables in it // + ////////////////////////////////////////////////////////////////////////////// + SavedReport savedReport = null; + if(input.getQueryParams().containsKey("savedReportId")) + { + QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get("savedReportId")))); + savedReport = new SavedReport(record); + } + else if(input.getQueryParams().containsKey("id")) + { + QRecord scheduledReportRecord = new GetAction().executeForRecord(new GetInput(ScheduledReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get("id")))); + QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(scheduledReportRecord.getValueInteger("savedReportId")))); + savedReport = new SavedReport(record); + + String inputValues = scheduledReportRecord.getValueString("inputValues"); + if(StringUtils.hasContent(inputValues)) + { + JSONObject jsonObject = JsonUtils.toJSONObject(inputValues); + for(String key : jsonObject.keySet()) + { + defaultValues.put(key, jsonObject.optString(key)); + } + } + } + else + { + ////////////////////////////////// + // return quietly w/ nothing... // + ////////////////////////////////// + DynamicFormWidgetData widgetData = new DynamicFormWidgetData(); + return new RenderWidgetOutput(widgetData); + } + + if(StringUtils.hasContent(savedReport.getQueryFilterJson())) + { + QQueryFilter queryFilter = SavedReportToReportMetaDataAdapter.getQQueryFilter(savedReport.getQueryFilterJson()); + QTableMetaData table = QContext.getQInstance().getTable(savedReport.getTableName()); + + /////////////////////////////////////////////////////////////////////////////////////////////// + // find variables in the query filter; convert them to a list of fields for the dynamic form // + /////////////////////////////////////////////////////////////////////////////////////////////// + for(QFilterCriteria criteria : CollectionUtils.nonNullList(queryFilter.getCriteria())) + { + ///////////////////////////////// + // todo - only variable fields // + ///////////////////////////////// + + //////////////////////////////// + // todo - twice for "between" // + //////////////////////////////// + + ////////////////////////// + // todo - join fields!! // + ////////////////////////// + QFieldMetaData fieldMetaData = table.getField(criteria.getFieldName()).clone(); + + ///////////////////////////////// + // make name & label for field // + ///////////////////////////////// + String operatorHumanish = StringUtils.allCapsToMixedCase(criteria.getOperator().name()); // todo match frontend..? + String fieldName = criteria.getFieldName() + operatorHumanish.replaceAll("_", ""); + String label = fieldMetaData.getLabel() + " " + operatorHumanish.replaceAll("_", " "); + fieldMetaData.setName(fieldName); + fieldMetaData.setLabel(label); + + //////////////////////////////////////////////////////////// + // in this use case, every field is required and editable // + //////////////////////////////////////////////////////////// + fieldMetaData.setIsRequired(true); + fieldMetaData.setIsEditable(true); + + if(defaultValues.containsKey(fieldName)) + { + fieldMetaData.setDefaultValue(defaultValues.get(fieldName)); + } + + fieldList.add(fieldMetaData); + } + } + + /////////////////////////////////// + // make output object and return // + /////////////////////////////////// + DynamicFormWidgetData widgetData = new DynamicFormWidgetData(); + widgetData.setFieldList(fieldList); + widgetData.setMergedDynamicFormValuesIntoFieldName("inputValues"); + + if(CollectionUtils.nullSafeIsEmpty(fieldList)) + { + widgetData.setNoFieldsMessage("This Report does not use any Variable Values"); + } + + return new RenderWidgetOutput(widgetData); + } + catch(Exception e) + { + LOG.warn("Error rendering scheduled report values dynamic form widget", e, logPair("queryParams", String.valueOf(input.getQueryParams()))); + throw (new QException("Error rendering scheduled report values dynamic form widget", e)); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportExecuteStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportExecuteStep.java new file mode 100644 index 00000000..d3ee6854 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportExecuteStep.java @@ -0,0 +1,105 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.processes.implementations.savedreports; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; +import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory; +import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; +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.model.actions.processes.RunBackendStepInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.savedreports.ScheduledReport; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RunScheduledReportExecuteStep implements BackendStep +{ + private static final QLogger LOG = QLogger.getLogger(RunScheduledReportExecuteStep.class); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException + { + Integer scheduledReportId = null; + try + { + List records = runBackendStepInput.getRecords(); + if(!CollectionUtils.nullSafeHasContents(records)) + { + throw (new QUserFacingException("No scheduled report was selected or found.")); + } + + ScheduledReport scheduledReport = new ScheduledReport(records.get(0)); + scheduledReportId = scheduledReport.getId(); + + ///////////////////////////////////////////// + // run the process that renders the report // + ///////////////////////////////////////////// + RunProcessAction runProcessAction = new RunProcessAction(); + RunProcessInput renderProcessInput = new RunProcessInput(); + renderProcessInput.setProcessName(RenderSavedReportMetaDataProducer.NAME); + renderProcessInput.setCallback(QProcessCallbackFactory.forPrimaryKey("id", scheduledReport.getSavedReportId())); + renderProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP); + renderProcessInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback()); + + renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, scheduledReport.getFormat()); + renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS, scheduledReport.getToAddresses()); + renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_SUBJECT, scheduledReport.getSubject()); + + if(StringUtils.hasContent(scheduledReport.getInputValues())) + { + ////////////////////////// + // todo variable-values // + ////////////////////////// + } + + RunProcessOutput renderProcessOutput = runProcessAction.execute(renderProcessInput); + } + catch(QUserFacingException ufe) + { + LOG.info("Error running scheduled report", ufe, logPair("id", scheduledReportId)); + throw (ufe); + } + catch(Exception e) + { + LOG.warn("Error running scheduled report", e, logPair("id", scheduledReportId)); + throw (new QException("Error running scheduled report", e)); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportMetaDataProducer.java new file mode 100644 index 00000000..15717f22 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RunScheduledReportMetaDataProducer.java @@ -0,0 +1,76 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.processes.implementations.savedreports; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; +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.dashboard.nocode.WidgetHtmlLine; +import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; +import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; +import com.kingsrook.qqq.backend.core.model.savedreports.ScheduledReport; + + +/******************************************************************************* + ** define process for rendering scheduled reports - that is - a thin layer on + ** top of rendering a saved report. + *******************************************************************************/ +public class RunScheduledReportMetaDataProducer implements MetaDataProducerInterface +{ + public static final String NAME = "runScheduledReport"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QProcessMetaData produce(QInstance qInstance) throws QException + { + QProcessMetaData process = new QProcessMetaData() + .withName(NAME) + .withLabel("Run Scheduled Report") + .withTableName(ScheduledReport.TABLE_NAME) + .withIcon(new QIcon().withName("print")) + + .addStep(new QBackendStepMetaData() + .withName("execute") + .withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData() + .withTableName(ScheduledReport.TABLE_NAME))) + .withCode(new QCodeReference(RunScheduledReportExecuteStep.class))) + + .addStep(new QFrontendStepMetaData() + .withName("results") + .withComponent(new NoCodeWidgetFrontendComponentMetaData() + .withOutput(new WidgetHtmlLine().withVelocityTemplate("Success")))); // todo!!! + + return (process); + } + +}