make processes able to render a no-code widget output! also search on custom PVS's

This commit is contained in:
2023-02-22 17:50:06 -06:00
parent 4cf8e37e7e
commit 7ea1750800
10 changed files with 368 additions and 21 deletions

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -35,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWi
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetValueSource; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.QNoCodeWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.QNoCodeWidgetMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils; import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -55,18 +57,12 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
{ {
QNoCodeWidgetMetaData widgetMetaData = (QNoCodeWidgetMetaData) input.getWidgetMetaData(); QNoCodeWidgetMetaData widgetMetaData = (QNoCodeWidgetMetaData) input.getWidgetMetaData();
//////////////////////////////////////////// Map<String, Object> context = initContext(input);
// build context by evaluating all values // context.putAll(input.getQueryParams());
////////////////////////////////////////////
Map<String, Object> context = new HashMap<>();
context.put("utils", new NoCodeWidgetVelocityUtils(context, input));
context.put("input", input);
for(Map.Entry<String, String> entry : input.getQueryParams().entrySet())
{
context.put(entry.getKey(), entry.getValue());
}
///////////////////////////////////////////////
// populate context by evaluating all values //
///////////////////////////////////////////////
for(AbstractWidgetValueSource valueSource : widgetMetaData.getValues()) for(AbstractWidgetValueSource valueSource : widgetMetaData.getValues())
{ {
try try
@ -86,8 +82,34 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
///////////////////////////////////////////// /////////////////////////////////////////////
// build content by evaluating all outputs // // build content by evaluating all outputs //
///////////////////////////////////////////// /////////////////////////////////////////////
List<AbstractWidgetOutput> outputs = widgetMetaData.getOutputs();
String content = renderOutputs(context, outputs);
return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content)));
}
/*******************************************************************************
**
*******************************************************************************/
public Map<String, Object> initContext(RenderWidgetInput input)
{
Map<String, Object> context = new HashMap<>();
context.put("utils", new NoCodeWidgetVelocityUtils(context, input));
context.put("input", input);
return context;
}
/*******************************************************************************
**
*******************************************************************************/
public String renderOutputs(Map<String, Object> context, List<AbstractWidgetOutput> outputs) throws QException
{
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
for(AbstractWidgetOutput output : widgetMetaData.getOutputs()) for(AbstractWidgetOutput output : CollectionUtils.nonNullList(outputs))
{ {
boolean conditionPassed = true; boolean conditionPassed = true;
if(output.getCondition() != null) if(output.getCondition() != null)
@ -106,8 +128,7 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
LOG.trace("Condition failed - not rendering this output."); LOG.trace("Condition failed - not rendering this output.");
} }
} }
return (content.toString());
return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content.toString())));
} }

View File

@ -277,4 +277,24 @@ public class NoCodeWidgetVelocityUtils
{ {
return String.valueOf(input.setScale(digits, RoundingMode.HALF_UP)); return String.valueOf(input.setScale(digits, RoundingMode.HALF_UP));
} }
/*******************************************************************************
**
*******************************************************************************/
public Object ifElse(Object ifObject, Object elseObject)
{
if(StringUtils.hasContent(ValueUtils.getValueAsString(ifObject)))
{
return (ifObject);
}
else if(StringUtils.hasContent(ValueUtils.getValueAsString(elseObject)))
{
return (elseObject);
}
return ("");
}
} }

View File

@ -31,6 +31,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
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.tables.UpdateAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
@ -50,7 +51,9 @@ 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.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.processes.NoCodeWidgetFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
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.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
@ -154,6 +157,7 @@ public class RunProcessAction
{ {
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName()); LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, frontendStep); processFrontendStepFieldDefaultValues(processState, frontendStep);
processFrontendComponents(processState, frontendStep);
processState.setNextStepName(step.getName()); processState.setNextStepName(step.getName());
break STEP_LOOP; break STEP_LOOP;
} }
@ -231,6 +235,26 @@ public class RunProcessAction
/*******************************************************************************
**
*******************************************************************************/
private void processFrontendComponents(ProcessState processState, QFrontendStepMetaData frontendStep) throws QException
{
for(QFrontendComponentMetaData component : CollectionUtils.nonNullList(frontendStep.getComponents()))
{
if(component instanceof NoCodeWidgetFrontendComponentMetaData noCodeWidgetComponent)
{
NoCodeWidgetRenderer noCodeWidgetRenderer = new NoCodeWidgetRenderer();
Map<String, Object> context = noCodeWidgetRenderer.initContext(null);
context.putAll(processState.getValues());
String html = noCodeWidgetRenderer.renderOutputs(context, noCodeWidgetComponent.getOutputs());
processState.getValues().put(frontendStep.getName() + ".html", html);
}
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -23,7 +23,14 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
@ -32,12 +39,65 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
*******************************************************************************/ *******************************************************************************/
public interface QCustomPossibleValueProvider public interface QCustomPossibleValueProvider
{ {
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
QPossibleValue<?> getPossibleValue(Serializable idValue); QPossibleValue<?> getPossibleValue(Serializable idValue);
// todo - get/search list of possible values /*******************************************************************************
**
*******************************************************************************/
List<QPossibleValue<?>> search(SearchPossibleValueSourceInput input) throws QException;
/*******************************************************************************
** The input list of ids might come through as a type that isn't the same as
** the type of the ids in the enum (e.g., strings from a frontend, integers
** in an enum). So, this method looks maps a list of input ids to the requested type.
*******************************************************************************/
default <T extends Serializable> List<T> convertInputIdsToIdType(Class<T> type, List<Serializable> inputIdList)
{
List<T> rs = new ArrayList<>();
if(CollectionUtils.nullSafeIsEmpty(inputIdList))
{
return (rs);
}
for(Serializable serializable : inputIdList)
{
rs.add(ValueUtils.getValueAsType(type, serializable));
}
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
default <T extends Serializable> boolean doesPossibleValueMatchSearchInput(Class<T> idType, QPossibleValue<T> possibleValue, SearchPossibleValueSourceInput input)
{
List<T> idsInType = convertInputIdsToIdType(idType, input.getIdList());
boolean match = false;
if(input.getIdList() != null)
{
if(idsInType.contains(possibleValue.getId()))
{
match = true;
}
}
else
{
if(StringUtils.hasContent(input.getSearchTerm()))
{
match = possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase());
}
else
{
match = true;
}
}
return match;
}
} }

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; 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.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -275,8 +276,12 @@ public class SearchPossibleValueSourceAction
{ {
try try
{ {
// QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
// return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value))); List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
output.setResults(possibleValues);
return (output);
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.values;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -31,7 +32,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
/******************************************************************************* /*******************************************************************************
** Input for the Search possible value source action ** Input for the Search possible value source action
*******************************************************************************/ *******************************************************************************/
public class SearchPossibleValueSourceInput extends AbstractActionInput public class SearchPossibleValueSourceInput extends AbstractActionInput implements Cloneable
{ {
private String possibleValueSourceName; private String possibleValueSourceName;
private QQueryFilter defaultQueryFilter; private QQueryFilter defaultQueryFilter;
@ -52,6 +53,33 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput
/*******************************************************************************
**
*******************************************************************************/
@Override
public SearchPossibleValueSourceInput clone()
{
try
{
SearchPossibleValueSourceInput clone = (SearchPossibleValueSourceInput) super.clone();
if(defaultQueryFilter != null)
{
clone.setDefaultQueryFilter(defaultQueryFilter.clone());
}
if(idList != null)
{
clone.setIdList(new ArrayList<>(idList));
}
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/******************************************************************************* /*******************************************************************************
** Getter for possibleValueSourceName ** Getter for possibleValueSourceName
** **
@ -253,5 +281,4 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput
this.limit = limit; this.limit = limit;
return (this); return (this);
} }
} }

View File

@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
@ -43,6 +45,13 @@ public class HtmlWrapper implements Serializable
<hr style="opacity: 0.25; height: 0.0625rem; border-width: 0; margin-bottom: 1rem; background-image: linear-gradient(to right, rgba(52, 71, 103, 0), rgba(52, 71, 103, 0.4), rgba(52, 71, 103, 0));" /> <hr style="opacity: 0.25; height: 0.0625rem; border-width: 0; margin-bottom: 1rem; background-image: linear-gradient(to right, rgba(52, 71, 103, 0), rgba(52, 71, 103, 0.4), rgba(52, 71, 103, 0));" />
""", ""); """, "");
public static final String STYLE_BIG_CENTERED = "font-size: 2rem; font-weight: 400; line-height: 1.625; text-align: center; padding-bottom: 8px; ";
public static final String STYLE_MEDIUM_CENTERED = "font-size: 1.5rem; font-weight: 400; line-height: 1.625; text-align: center; padding-bottom: 4px; ";
public static final String STYLE_INDENT_1 = "padding-left: 1rem; ";
public static final String STYLE_INDENT_2 = "padding-left: 2rem; ";
public static final String STYLE_FLOAT_RIGHT = "float: right; ";
public static final String STYLE_RED = "color: red; ";
/******************************************************************************* /*******************************************************************************
@ -56,6 +65,27 @@ public class HtmlWrapper implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public static HtmlWrapper divWithStyles(String... styles)
{
String style = StringUtils.join("", Arrays.asList(styles));
return (new HtmlWrapper("<div style=\"" + style + "\">", "</div>"));
}
/*******************************************************************************
**
*******************************************************************************/
public static HtmlWrapper width(String amount)
{
return (new HtmlWrapper("<div style='width: " + amount + "'>", "</div>"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -66,6 +96,16 @@ public class HtmlWrapper implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public static String styleWidth(String amount)
{
return ("width: " + amount);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -76,4 +116,47 @@ public class HtmlWrapper implements Serializable
+ Objects.requireNonNullElse(suffix, "") + "\n"); + Objects.requireNonNullElse(suffix, "") + "\n");
} }
/*******************************************************************************
** Getter for prefix
**
*******************************************************************************/
public String getPrefix()
{
return prefix;
}
/*******************************************************************************
** Setter for prefix
**
*******************************************************************************/
public void setPrefix(String prefix)
{
this.prefix = prefix;
}
/*******************************************************************************
** Getter for suffix
**
*******************************************************************************/
public String getSuffix()
{
return suffix;
}
/*******************************************************************************
** Setter for suffix
**
*******************************************************************************/
public void setSuffix(String suffix)
{
this.suffix = suffix;
}
} }

View File

@ -0,0 +1,94 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.processes;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetOutput;
/*******************************************************************************
**
*******************************************************************************/
public class NoCodeWidgetFrontendComponentMetaData extends QFrontendComponentMetaData
{
private List<AbstractWidgetOutput> outputs = new ArrayList<>();
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public NoCodeWidgetFrontendComponentMetaData()
{
setType(QComponentType.HTML);
}
/*******************************************************************************
** Fluent setter to add a single output
*******************************************************************************/
public NoCodeWidgetFrontendComponentMetaData withOutput(AbstractWidgetOutput output)
{
if(this.outputs == null)
{
this.outputs = new ArrayList<>();
}
this.outputs.add(output);
return (this);
}
/*******************************************************************************
** Getter for outputs
*******************************************************************************/
public List<AbstractWidgetOutput> getOutputs()
{
return (this.outputs);
}
/*******************************************************************************
** Setter for outputs
*******************************************************************************/
public void setOutputs(List<AbstractWidgetOutput> outputs)
{
this.outputs = outputs;
}
/*******************************************************************************
** Fluent setter for outputs
*******************************************************************************/
public NoCodeWidgetFrontendComponentMetaData withOutputs(List<AbstractWidgetOutput> outputs)
{
this.outputs = outputs;
return (this);
}
}

View File

@ -35,7 +35,8 @@ public enum QComponentType
DOWNLOAD_FORM, DOWNLOAD_FORM,
RECORD_LIST, RECORD_LIST,
PROCESS_SUMMARY_RESULTS, PROCESS_SUMMARY_RESULTS,
GOOGLE_DRIVE_SELECT_FOLDER; GOOGLE_DRIVE_SELECT_FOLDER,
HTML;
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// keep these values in sync with QComponentType.ts in qqq-frontend-core // // keep these values in sync with QComponentType.ts in qqq-frontend-core //
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

View File

@ -50,6 +50,7 @@ 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.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.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput; import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
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.QAuthenticationType; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
@ -1093,6 +1094,17 @@ public class TestUtils
{ {
return (new QPossibleValue<>(idValue, "Custom[" + idValue + "]")); return (new QPossibleValue<>(idValue, "Custom[" + idValue + "]"));
} }
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QPossibleValue<?>> search(SearchPossibleValueSourceInput input)
{
return (new ArrayList<>());
}
} }