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.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.QNoCodeWidgetMetaData;
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;
@ -55,18 +57,12 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
{
QNoCodeWidgetMetaData widgetMetaData = (QNoCodeWidgetMetaData) input.getWidgetMetaData();
////////////////////////////////////////////
// build context by evaluating all values //
////////////////////////////////////////////
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());
}
Map<String, Object> context = initContext(input);
context.putAll(input.getQueryParams());
///////////////////////////////////////////////
// populate context by evaluating all values //
///////////////////////////////////////////////
for(AbstractWidgetValueSource valueSource : widgetMetaData.getValues())
{
try
@ -86,8 +82,34 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
/////////////////////////////////////////////
// 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();
for(AbstractWidgetOutput output : widgetMetaData.getOutputs())
for(AbstractWidgetOutput output : CollectionUtils.nonNullList(outputs))
{
boolean conditionPassed = true;
if(output.getCondition() != null)
@ -106,8 +128,7 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
LOG.trace("Condition failed - not rendering this output.");
}
}
return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content.toString())));
return (content.toString());
}

View File

@ -277,4 +277,24 @@ public class NoCodeWidgetVelocityUtils
{
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.UUID;
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.QueryAction;
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.data.QRecord;
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.QFrontendComponentMetaData;
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.QStepMetaData;
@ -154,6 +157,7 @@ public class RunProcessAction
{
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, frontendStep);
processFrontendComponents(processState, frontendStep);
processState.setNextStepName(step.getName());
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.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.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
{
/*******************************************************************************
**
*******************************************************************************/
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.List;
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.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -275,8 +276,12 @@ public class SearchPossibleValueSourceAction
{
try
{
// QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
// return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
output.setResults(possibleValues);
return (output);
}
catch(Exception e)
{

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.values;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
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
*******************************************************************************/
public class SearchPossibleValueSourceInput extends AbstractActionInput
public class SearchPossibleValueSourceInput extends AbstractActionInput implements Cloneable
{
private String possibleValueSourceName;
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
**
@ -253,5 +281,4 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput
this.limit = limit;
return (this);
}
}

View File

@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode;
import java.io.Serializable;
import java.util.Arrays;
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));" />
""", "");
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");
}
/*******************************************************************************
** 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,
RECORD_LIST,
PROCESS_SUMMARY_RESULTS,
GOOGLE_DRIVE_SELECT_FOLDER;
GOOGLE_DRIVE_SELECT_FOLDER,
HTML;
///////////////////////////////////////////////////////////////////////////
// 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.QueryOutput;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
@ -1093,6 +1094,17 @@ public class TestUtils
{
return (new QPossibleValue<>(idValue, "Custom[" + idValue + "]"));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QPossibleValue<?>> search(SearchPossibleValueSourceInput input)
{
return (new ArrayList<>());
}
}