Merged dev into feature/meta-data-loaders

This commit is contained in:
2025-03-08 20:05:25 -06:00
346 changed files with 33987 additions and 5327 deletions

View File

@ -95,8 +95,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
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.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
@ -1200,15 +1198,18 @@ public class QJavalinImplementation
/////////////////////////////////////////////////////////////////////////////////////
if(backend != null && backend.getUsesVariants())
{
queryInput.setTableName(backend.getVariantOptionsTableName());
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(backend.getVariantOptionsTableTypeField(), QCriteriaOperator.EQUALS, backend.getVariantOptionsTableTypeValue())));
QTableMetaData variantsTable = QContext.getQInstance().getTable(backend.getBackendVariantsConfig().getOptionsTableName());
queryInput.setTableName(variantsTable.getName());
queryInput.setFilter(backend.getBackendVariantsConfig().getOptionsFilter());
queryInput.setShouldGenerateDisplayValues(true);
QueryOutput output = new QueryAction().execute(queryInput);
for(QRecord qRecord : output.getRecords())
{
variants.add(new QFrontendVariant()
.withId(qRecord.getValue(backend.getVariantOptionsTableIdField()))
.withType(backend.getVariantOptionsTableTypeValue())
.withName(qRecord.getValueString(backend.getVariantOptionsTableNameField())));
.withId(qRecord.getValue(variantsTable.getPrimaryKeyField()))
.withType(backend.getBackendVariantsConfig().getVariantTypeKey())
.withName(qRecord.getRecordLabel()));
}
QJavalinAccessLogger.logStartSilent("variants");

View File

@ -26,6 +26,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.time.LocalDate;
@ -50,24 +51,22 @@ import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessAction;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
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.actions.reporting.ReportDestination;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -79,9 +78,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMeta
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
@ -93,6 +89,7 @@ import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.Context;
import io.javalin.http.UploadedFile;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.jetty.http.HttpStatus;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
import static io.javalin.apibuilder.ApiBuilder.get;
@ -325,7 +322,7 @@ public class QJavalinProcessHandler
*******************************************************************************/
public static void processInit(Context context)
{
doProcessInitOrStep(context, null, null, RunProcessInput.FrontendStepBehavior.BREAK);
doProcessInitOrStep(context, null, null, null, RunProcessInput.FrontendStepBehavior.BREAK);
}
@ -339,7 +336,7 @@ public class QJavalinProcessHandler
*******************************************************************************/
public static void processRun(Context context)
{
doProcessInitOrStep(context, null, null, RunProcessInput.FrontendStepBehavior.SKIP);
doProcessInitOrStep(context, null, null, null, RunProcessInput.FrontendStepBehavior.SKIP);
}
@ -347,7 +344,7 @@ public class QJavalinProcessHandler
/*******************************************************************************
**
*******************************************************************************/
private static void doProcessInitOrStep(Context context, String processUUID, String startAfterStep, RunProcessInput.FrontendStepBehavior frontendStepBehavior)
private static void doProcessInitOrStep(Context context, String processUUID, String startAfterStep, String startAtStep, RunProcessInput.FrontendStepBehavior frontendStepBehavior)
{
Map<String, Object> resultForCaller = new HashMap<>();
Exception returningException = null;
@ -362,8 +359,22 @@ public class QJavalinProcessHandler
}
resultForCaller.put("processUUID", processUUID);
LOG.info(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]"
: "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
if(startAfterStep == null && startAtStep == null)
{
LOG.info("Initiating process [" + processName + "] [" + processUUID + "]");
}
else if(startAfterStep != null)
{
LOG.info("Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
}
else if(startAtStep != null)
{
LOG.info("Resuming process [" + processName + "] [" + processUUID + "] at step [" + startAtStep + "]");
}
else
{
LOG.warn("A logical impossibility was reached, regarding the nullity of startAfterStep and startAtStep, at least given how this code was originally written.");
}
RunProcessInput runProcessInput = new RunProcessInput();
QJavalinImplementation.setupSession(context, runProcessInput);
@ -372,11 +383,13 @@ public class QJavalinProcessHandler
runProcessInput.setFrontendStepBehavior(frontendStepBehavior);
runProcessInput.setProcessUUID(processUUID);
runProcessInput.setStartAfterStep(startAfterStep);
runProcessInput.setStartAtStep(startAtStep);
populateRunProcessRequestWithValuesFromContext(context, runProcessInput);
String reportName = ValueUtils.getValueAsString(runProcessInput.getValue("reportName"));
QJavalinAccessLogger.logStart(startAfterStep == null ? "processInit" : "processStep", logPair("processName", processName), logPair("processUUID", processUUID),
StringUtils.hasContent(startAfterStep) ? logPair("startAfterStep", startAfterStep) : null,
StringUtils.hasContent(startAtStep) ? logPair("startAtStep", startAfterStep) : null,
StringUtils.hasContent(reportName) ? logPair("reportName", reportName) : null);
//////////////////////////////////////////////////////////////////////////////////////////////////
@ -485,6 +498,7 @@ public class QJavalinProcessHandler
}
resultForCaller.put("values", runProcessOutput.getValues());
runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
runProcessOutput.getProcessState().getBackStepName().ifPresent(backStep -> resultForCaller.put("backStep", backStep));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// todo - delete after all frontends look for processMetaDataAdjustment instead of updatedFrontendStepList //
@ -531,7 +545,7 @@ public class QJavalinProcessHandler
** todo - make query params have a "field-" type of prefix??
**
*******************************************************************************/
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessInput runProcessInput) throws IOException
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessInput runProcessInput) throws IOException, QException
{
//////////////////////////
// process query string //
@ -562,20 +576,42 @@ public class QJavalinProcessHandler
////////////////////////////
// process uploaded files //
////////////////////////////
for(UploadedFile uploadedFile : context.uploadedFiles())
for(Map.Entry<String, List<UploadedFile>> entry : context.uploadedFileMap().entrySet())
{
try(InputStream content = uploadedFile.content())
String name = entry.getKey();
List<UploadedFile> uploadedFiles = entry.getValue();
ArrayList<StorageInput> storageInputs = new ArrayList<>();
runProcessInput.addValue(name, storageInputs);
String storageTableName = QJavalinImplementation.javalinMetaData.getUploadedFileArchiveTableName();
if(!StringUtils.hasContent(storageTableName))
{
QUploadedFile qUploadedFile = new QUploadedFile();
qUploadedFile.setBytes(content.readAllBytes());
qUploadedFile.setFilename(uploadedFile.filename());
throw (new QException("UploadFileArchiveTableName was not specified in javalinMetaData. Cannot accept file uploads."));
}
UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
TempFileStateProvider.getInstance().put(key, qUploadedFile);
LOG.info("Stored uploaded file in TempFileStateProvider under key: " + key);
runProcessInput.addValue(QUploadedFile.DEFAULT_UPLOADED_FILE_FIELD_NAME, key);
for(UploadedFile uploadedFile : uploadedFiles)
{
String reference = QValueFormatter.formatDate(LocalDate.now())
+ File.separator + runProcessInput.getProcessName()
+ File.separator + UUID.randomUUID()
+ File.separator + uploadedFile.filename();
archiveUploadedFile(runProcessInput, qUploadedFile);
StorageInput storageInput = new StorageInput(storageTableName).withReference(reference);
storageInputs.add(storageInput);
try
(
InputStream content = uploadedFile.content();
OutputStream outputStream = new StorageAction().createOutputStream(storageInput);
)
{
content.transferTo(outputStream);
LOG.info("Streamed uploaded file", logPair("storageTable", storageTableName), logPair("reference", reference), logPair("processName", runProcessInput.getProcessName()), logPair("uploadFileName", uploadedFile.filename()));
}
catch(QException e)
{
throw (new QException("Error creating output stream in table [" + storageTableName + "] for storage action", e));
}
}
}
@ -606,27 +642,6 @@ public class QJavalinProcessHandler
/*******************************************************************************
**
*******************************************************************************/
private static void archiveUploadedFile(RunProcessInput runProcessInput, QUploadedFile qUploadedFile)
{
String fileName = QValueFormatter.formatDate(LocalDate.now())
+ File.separator + runProcessInput.getProcessName()
+ File.separator + qUploadedFile.getFilename();
InsertInput insertInput = new InsertInput();
insertInput.setTableName(QJavalinImplementation.javalinMetaData.getUploadedFileArchiveTableName());
insertInput.setRecords(List.of(new QRecord()
.withValue("fileName", fileName)
.withValue("contents", qUploadedFile.getBytes())
));
new InsertAction().executeAsync(insertInput);
}
/*******************************************************************************
**
*******************************************************************************/
@ -684,8 +699,19 @@ public class QJavalinProcessHandler
public static void processStep(Context context)
{
String processUUID = context.pathParam("processUUID");
String lastStep = context.pathParam("step");
doProcessInitOrStep(context, processUUID, lastStep, RunProcessInput.FrontendStepBehavior.BREAK);
String startAfterStep = null;
String startAtStep = null;
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(context.queryParam("isStepBack"))))
{
startAtStep = context.pathParam("step");
}
else
{
startAfterStep = context.pathParam("step");
}
doProcessInitOrStep(context, processUUID, startAfterStep, startAtStep, RunProcessInput.FrontendStepBehavior.BREAK);
}

View File

@ -77,7 +77,8 @@ public class QApplicationJavalinServer
private boolean serveLegacyUnversionedMiddlewareAPI = true;
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1());
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null;
private Consumer<Javalin> javalinConfigurationCustomizer = null;
private Consumer<Javalin> javalinConfigurationCustomizer = null;
private QJavalinMetaData javalinMetaData = null;
private long lastQInstanceHotSwapMillis;
private long millisBetweenHotSwaps = 2500;
@ -115,6 +116,11 @@ public class QApplicationJavalinServer
{
if(serveFrontendMaterialDashboard)
{
if(getClass().getResource("/material-dashboard/index.html") == null)
{
LOG.warn("/material-dashboard/index.html resource was not found. This might happen if you're using a local (e.g., within-IDE) snapshot version... Try updating pom.xml to reference a released version of qfmd?");
}
////////////////////////////////////////////////////////////////////////////////////////
// If you have any assets to add to the web server (e.g., logos, icons) place them at //
// src/main/resources/material-dashboard-overlay //
@ -149,7 +155,7 @@ public class QApplicationJavalinServer
{
try
{
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance);
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance, javalinMetaData);
config.router.apiBuilder(qJavalinImplementation.getRoutes());
}
catch(QInstanceValidationException e)
@ -623,4 +629,35 @@ public class QApplicationJavalinServer
return (this);
}
/*******************************************************************************
** Getter for javalinMetaData
*******************************************************************************/
public QJavalinMetaData getJavalinMetaData()
{
return (this.javalinMetaData);
}
/*******************************************************************************
** Setter for javalinMetaData
*******************************************************************************/
public void setJavalinMetaData(QJavalinMetaData javalinMetaData)
{
this.javalinMetaData = javalinMetaData;
}
/*******************************************************************************
** Fluent setter for javalinMetaData
*******************************************************************************/
public QApplicationJavalinServer withJavalinMetaData(QJavalinMetaData javalinMetaData)
{
this.javalinMetaData = javalinMetaData;
return (this);
}
}

View File

@ -45,6 +45,7 @@ public class ProcessInitOrStepInput extends AbstractMiddlewareInput
/////////////////////////////////////
private String processUUID;
private String startAfterStep;
// todo - add (in next version?) startAtStep (for back)
private RunProcessInput.FrontendStepBehavior frontendStepBehavior = RunProcessInput.FrontendStepBehavior.BREAK;

View File

@ -58,6 +58,8 @@ public interface ProcessInitOrStepOrStatusOutputInterface extends AbstractMiddle
*******************************************************************************/
void setNextStep(String nextStep);
// todo - add (in next version?) backStep
/*******************************************************************************
** Setter for values
*******************************************************************************/

View File

@ -38,6 +38,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIEnumSubSet;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIHasAdditionalProperties;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIIncludeProperties;
@ -123,7 +124,25 @@ public class SchemaBuilder
if(c.isEnum())
{
schema.withType(Type.STRING);
schema.withEnumValues(Arrays.stream(c.getEnumConstants()).map(e -> String.valueOf(e)).collect(Collectors.toList()));
if(element.isAnnotationPresent(OpenAPIEnumSubSet.class))
{
try
{
OpenAPIEnumSubSet enumSubSetAnnotation = element.getAnnotation(OpenAPIEnumSubSet.class);
Class<? extends OpenAPIEnumSubSet.EnumSubSet<?>> enumSubSetClass = enumSubSetAnnotation.value();
OpenAPIEnumSubSet.EnumSubSet<?> enumSubSetContainer = enumSubSetClass.getConstructor().newInstance();
schema.withEnumValues(enumSubSetContainer.getSubSet().stream().map(e -> String.valueOf(e)).collect(Collectors.toList()));
}
catch(Exception e)
{
throw new QRuntimeException("Error processing OpenAPIEnumSubSet on element: " + element, e);
}
}
else
{
schema.withEnumValues(Arrays.stream(c.getEnumConstants()).map(e -> String.valueOf(e)).collect(Collectors.toList()));
}
}
else if(c.equals(String.class))
{

View File

@ -0,0 +1,116 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIEnumSubSet;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
/*******************************************************************************
**
*******************************************************************************/
public class FieldAdornment implements ToSchema
{
@OpenAPIExclude()
private com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment wrapped;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public FieldAdornment(com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment wrapped)
{
this.wrapped = wrapped;
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public FieldAdornment()
{
}
/***************************************************************************
**
***************************************************************************/
public static class FieldAdornmentSubSet implements OpenAPIEnumSubSet.EnumSubSet<AdornmentType>
{
private static EnumSet<AdornmentType> subSet = null;
/***************************************************************************
**
***************************************************************************/
@Override
public EnumSet<AdornmentType> getSubSet()
{
if(subSet == null)
{
EnumSet<AdornmentType> subSet = EnumSet.allOf(AdornmentType.class);
subSet.remove(AdornmentType.FILE_UPLOAD); // todo - remove for next version!
subSet.remove(AdornmentType.TOOLTIP); // todo - remove for next version!
FieldAdornmentSubSet.subSet = subSet;
}
return (subSet);
}
}
/***************************************************************************
**
***************************************************************************/
@OpenAPIDescription("Type of this adornment")
@OpenAPIEnumSubSet(FieldAdornmentSubSet.class)
public AdornmentType getType()
{
return (this.wrapped == null || this.wrapped.getType() == null ? null : this.wrapped.getType());
}
/***************************************************************************
**
***************************************************************************/
@OpenAPIDescription("Values associated with this adornment. Keys and the meanings of their values will differ by type.")
public Map<String, Serializable> getValues()
{
return (this.wrapped.getValues());
}
}

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
@ -61,6 +60,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -82,6 +82,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -92,6 +93,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -102,6 +104,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -112,6 +115,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -122,6 +126,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -143,6 +148,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -153,6 +159,7 @@ public class FieldMetaData implements ToSchema
}
/***************************************************************************
**
***************************************************************************/
@ -166,6 +173,8 @@ public class FieldMetaData implements ToSchema
// todo - inline PVS
/***************************************************************************
**
***************************************************************************/
@ -177,14 +186,17 @@ public class FieldMetaData implements ToSchema
// todo behaviors?
/***************************************************************************
**
***************************************************************************/
@OpenAPIDescription("Special UI dressings to add to the field.")
@OpenAPIListItems(value = FieldAdornment.class) // todo!
@OpenAPIListItems(value = FieldAdornment.class, useRef = true)
public List<FieldAdornment> getAdornments()
{
return (this.wrapped.getAdornments());
return (this.wrapped.getAdornments() == null ? null : this.wrapped.getAdornments().stream().map(a -> new FieldAdornment(a)).toList());
}
// todo help content

View File

@ -23,11 +23,13 @@ package com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIEnumSubSet;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIExclude;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIMapKnownEntries;
@ -63,10 +65,39 @@ public class FrontendComponent implements ToSchema
/***************************************************************************
**
***************************************************************************/
public static class QComponentTypeSubSet implements OpenAPIEnumSubSet.EnumSubSet<QComponentType>
{
private static EnumSet<QComponentType> subSet = null;
/***************************************************************************
**
***************************************************************************/
@Override
public EnumSet<QComponentType> getSubSet()
{
if(subSet == null)
{
EnumSet<QComponentType> subSet = EnumSet.allOf(QComponentType.class);
subSet.remove(QComponentType.BULK_LOAD_FILE_MAPPING_FORM);
subSet.remove(QComponentType.BULK_LOAD_VALUE_MAPPING_FORM);
subSet.remove(QComponentType.BULK_LOAD_PROFILE_FORM);
QComponentTypeSubSet.subSet = subSet;
}
return(subSet);
}
}
/***************************************************************************
**
***************************************************************************/
@OpenAPIDescription("The type of this component. e.g., what kind of UI element(s) should be presented to the user.")
@OpenAPIEnumSubSet(QComponentTypeSubSet.class)
public QComponentType getType()
{
return (this.wrapped.getType());

View File

@ -130,26 +130,31 @@ components:
description: "Description of the error"
type: "string"
type: "object"
FieldAdornment:
properties:
type:
description: "Type of this adornment"
enum:
- "LINK"
- "CHIP"
- "SIZE"
- "CODE_EDITOR"
- "RENDER_HTML"
- "REVEAL"
- "FILE_DOWNLOAD"
- "ERROR"
type: "string"
values:
description: "Values associated with this adornment. Keys and the meanings\
\ of their values will differ by type."
type: "object"
type: "object"
FieldMetaData:
properties:
adornments:
description: "Special UI dressings to add to the field."
items:
properties:
type:
enum:
- "LINK"
- "CHIP"
- "SIZE"
- "CODE_EDITOR"
- "RENDER_HTML"
- "REVEAL"
- "FILE_DOWNLOAD"
- "ERROR"
type: "string"
values:
type: "object"
type: "object"
$ref: "#/components/schemas/FieldAdornment"
type: "array"
defaultValue:
description: "Default value to use in this field."
@ -973,21 +978,7 @@ components:
adornments:
description: "Special UI dressings to add to the field."
items:
properties:
type:
enum:
- "LINK"
- "CHIP"
- "SIZE"
- "CODE_EDITOR"
- "RENDER_HTML"
- "REVEAL"
- "FILE_DOWNLOAD"
- "ERROR"
type: "string"
values:
type: "object"
type: "object"
$ref: "#/components/schemas/FieldAdornment"
type: "array"
defaultValue:
description: "Default value to use in this field."
@ -1483,6 +1474,8 @@ paths:
processes:
person.bulkInsert:
hasPermission: true
icon:
name: "library_add"
isHidden: true
label: "Person Bulk Insert"
name: "person.bulkInsert"

View File

@ -40,8 +40,10 @@ import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
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.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
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.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
@ -1382,6 +1384,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
Function<String, QInstance> makeNewInstanceWithBackendName = (backendName) ->
{
QInstance newInstance = new QInstance();
newInstance.setAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
newInstance.addBackend(new QBackendMetaData().withName(backendName).withBackendType(MockBackendModule.class));
if(!"invalid".equals(backendName))