Merged dev into feature/workflows-support

This commit is contained in:
2025-07-18 11:13:39 -05:00
42 changed files with 304 additions and 173 deletions

View File

@ -161,7 +161,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
public static String linkTableCreateWithDefaultValues(RenderWidgetInput input, String tableName, Map<String, Serializable> defaultValues) throws QException
{
String tablePath = QContext.getQInstance().getTablePath(tableName);
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), Charset.defaultCharset()));
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), StandardCharsets.UTF_8));
}
@ -229,7 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
}
filter = QQueryFilterDeduper.dedupeFilter(filter);
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
}

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -300,7 +301,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
String tablePath = QContext.getQInstance().getTablePath(rightTable.getName());
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
widgetData.setOmitFieldNames(omitFieldNames);

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -194,7 +195,7 @@ public class RecordListWidgetRenderer extends AbstractWidgetRenderer
}
String tablePath = QContext.getQInstance().getTablePath(tableName);
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
ChildRecordListData widgetData = new ChildRecordListData(input.getQueryParams().get("widgetLabel"), queryOutput, table, tablePath, viewAllLink, totalRows);

View File

@ -331,23 +331,25 @@ public class MetaDataAction
if(metaDataActionCustomizerReference != null)
{
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
}
if(actionCustomizer == null)
{
/////////////////////////////////////////////////////////////////////////////////////
// check if QInstance is still using the now-deprecated getMetaDataFilter approach //
/////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("deprecation")
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
if(metaDataFilterReference != null)
{
LOG.warn("QInstance.metaDataFilter is deprecated in favor of metaDataActionCustomizer.");
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
}
}
if(actionCustomizer == null)
{
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
}
return (actionCustomizer);

View File

@ -1028,16 +1028,16 @@ public class QInstanceEnricher
.withCode(new QCodeReference(BulkInsertReceiveValueMappingStep.class));
int i = 0;
process.addStep(i++, prepareFileUploadStep);
process.addStep(i++, uploadScreen);
process.withStep(i++, prepareFileUploadStep);
process.withStep(i++, uploadScreen);
process.addStep(i++, prepareFileMappingStep);
process.addStep(i++, fileMappingScreen);
process.addStep(i++, receiveFileMappingStep);
process.withStep(i++, prepareFileMappingStep);
process.withStep(i++, fileMappingScreen);
process.withStep(i++, receiveFileMappingStep);
process.addStep(i++, prepareValueMappingStep);
process.addStep(i++, valueMappingScreen);
process.addStep(i++, receiveValueMappingStep);
process.withStep(i++, prepareValueMappingStep);
process.withStep(i++, valueMappingScreen);
process.withStep(i++, receiveValueMappingStep);
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW).setRecordListFields(editableFields);
@ -1097,7 +1097,7 @@ public class QInstanceEnricher
Fields whose switches are off will not be updated."""))
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_EDIT_FORM));
process.addStep(0, editScreen);
process.withStep(0, editScreen);
process.getFrontendStep("review").setRecordListFields(editableFields);
qInstance.addProcess(process);
}

View File

@ -241,8 +241,10 @@ public class QInstanceValidator
/***************************************************************************
**
* this method still supports the deprecated MetaDataFilter (plus its
* replacement, MetaDataActionCustomizer
***************************************************************************/
@SuppressWarnings("deprecation")
private void validateInstanceAttributes(QInstance qInstance)
{
if(qInstance.getMetaDataFilter() != null)

View File

@ -213,7 +213,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
{
if(stepList != null)
{
stepList.forEach(this::addStep);
stepList.forEach(this::withStep);
}
return (this);
@ -231,7 +231,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
{
index = this.stepList.size();
}
addStep(index, step);
withStep(index, step);
return (this);
}

View File

@ -176,7 +176,7 @@ public class ScriptsMetaDataProvider
.withLoadStepClass(RunRecordScriptLoadStep.class)
.getProcessMetaData();
processMetaData.addStep(0, new QFrontendStepMetaData()
processMetaData.withStep(0, new QFrontendStepMetaData()
.withName("input")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
.withFormField(new QFieldMetaData("scriptId", QFieldType.INTEGER).withPossibleValueSourceName(Script.TABLE_NAME)

View File

@ -83,8 +83,8 @@ public class BasicETLProcess
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(extractFunction)
.addStep(transformFunction)
.addStep(loadFunction);
.withStep(extractFunction)
.withStep(transformFunction)
.withStep(loadFunction);
}
}

View File

@ -68,6 +68,6 @@ public class StreamedETLProcess
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(etlFunction);
.withStep(etlFunction);
}
}

View File

@ -191,11 +191,11 @@ public class StreamedETLWithFrontendProcess
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.PROCESS_SUMMARY_RESULTS));
return new QProcessMetaData()
.addStep(previewStep)
.addStep(reviewStep)
.addStep(validateStep)
.addStep(executeStep)
.addStep(resultStep);
.withStep(previewStep)
.withStep(reviewStep)
.withStep(validateStep)
.withStep(executeStep)
.withStep(resultStep);
}

View File

@ -91,7 +91,7 @@ public class GarbageCollectorProcessMetaDataProducer
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData("joinedTablesToAlsoDelete", QFieldType.STRING).withDefaultValue(joinedTablesToAlsoDelete)));
processMetaData.addStep(0, new QFrontendStepMetaData()
processMetaData.withStep(0, new QFrontendStepMetaData()
.withName("input")
.withLabel("Input")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.HELP_TEXT).withValue("text", """

View File

@ -81,10 +81,10 @@ public class BasicRunReportProcess
return new QProcessMetaData()
.withName(PROCESS_NAME)
.withIsHidden(true)
.addStep(prepareStep)
.addStep(inputStep)
.addStep(executeStep)
.addStep(accessStep);
.withStep(prepareStep)
.withStep(inputStep)
.withStep(executeStep)
.withStep(accessStep);
}
}

View File

@ -109,10 +109,10 @@ public class RunReportForRecordProcess
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(prepareStep)
.addStep(inputStep)
.addStep(executeStep)
.addStep(accessStep);
.withStep(prepareStep)
.withStep(inputStep)
.withStep(executeStep)
.withStep(accessStep);
}

View File

@ -75,7 +75,7 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withTableName(SavedReport.TABLE_NAME)
.withIcon(new QIcon().withName("print"))
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("pre")
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(SES_PROVIDER_NAME, QFieldType.STRING))
@ -86,7 +86,7 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("input")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
.withFormField(new QFieldMetaData(FIELD_NAME_REPORT_FORMAT, QFieldType.STRING)
@ -97,13 +97,13 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.WIDGET)
.withValue("widgetName", SavedReportsMetaDataProvider.RENDER_REPORT_PROCESS_VALUES_WIDGET)))
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("execute")
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
.withTableName(SavedReport.TABLE_NAME)))
.withCode(new QCodeReference(RenderSavedReportExecuteStep.class)))
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("output")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM))
.withComponent(new NoCodeWidgetFrontendComponentMetaData()

View File

@ -59,13 +59,13 @@ public class RunScheduledReportMetaDataProducer implements MetaDataProducerInter
.withTableName(ScheduledReport.TABLE_NAME)
.withIcon(new QIcon().withName("print"))
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("execute")
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
.withTableName(ScheduledReport.TABLE_NAME)))
.withCode(new QCodeReference(RunScheduledReportExecuteStep.class)))
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("results")
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Success")))); // todo!!!

View File

@ -37,6 +37,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -84,6 +85,7 @@ public class JsonUtils
** Internally using jackson - so jackson annotations apply!
**
*******************************************************************************/
@Deprecated(since = "since toJsonCustomized was added, which uses jackson's newer builder object for customization")
public static String toJson(Object object, Consumer<ObjectMapper> objectMapperCustomizer)
{
try
@ -105,6 +107,34 @@ public class JsonUtils
/*******************************************************************************
** Serialize any object into a JSON String - with customizations on the Jackson
** ObjectMapper.
**
** Internally using jackson - so jackson annotations apply!
**
*******************************************************************************/
public static String toJsonCustomized(Object object, Consumer<JsonMapper.Builder> jsonMapperCustomizer)
{
try
{
JsonMapper.Builder jsonMapperBuilder = newJsonMapperBuilder();
if(jsonMapperCustomizer != null)
{
jsonMapperCustomizer.accept(jsonMapperBuilder);
}
String jsonResult = jsonMapperBuilder.build().writeValueAsString(object);
return (jsonResult);
}
catch(JsonProcessingException e)
{
LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to json", e);
throw new IllegalArgumentException("Error in JSON Serialization", e);
}
}
/*******************************************************************************
** Serialize any object into a "pretty" / formatted JSON String.
**
@ -168,7 +198,6 @@ public class JsonUtils
/*******************************************************************************
** De-serialize a json string into an object of the specified class - with
** customizations on the Jackson ObjectMapper.
**.
**
** Internally using jackson - so jackson annotations apply!
**
@ -242,6 +271,23 @@ public class JsonUtils
/*******************************************************************************
** Standard private method to build jackson JsonMapperBuilder with standard features.
**
*******************************************************************************/
private static JsonMapper.Builder newJsonMapperBuilder()
{
JsonMapper.Builder jsonMapperBuilder = JsonMapper.builder();
jsonMapperBuilder.addModule(new JavaTimeModule());
jsonMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
jsonMapperBuilder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
jsonMapperBuilder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jsonMapperBuilder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return (jsonMapperBuilder);
}
/*******************************************************************************
** Standard private method to build jackson ObjectMapper with standard features.
**

View File

@ -26,9 +26,11 @@ import java.util.Map;
import java.util.function.Consumer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -62,7 +64,7 @@ public class YamlUtils
*******************************************************************************/
public static String toYaml(Object object)
{
return toYaml(object, null);
return toYamlCustomized(object, null);
}
@ -70,6 +72,7 @@ public class YamlUtils
/*******************************************************************************
**
*******************************************************************************/
@Deprecated(since = "since toYamlCustomized was added, which uses jackson's newer builder object for customization")
public static String toYaml(Object object, Consumer<ObjectMapper> objectMapperCustomizer)
{
try
@ -96,4 +99,36 @@ public class YamlUtils
}
}
/*******************************************************************************
**
*******************************************************************************/
public static String toYamlCustomized(Object object, Consumer<YAMLMapper.Builder> yamlMapperCustomizer)
{
try
{
YAMLFactory yamlFactory = new YAMLFactory()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
YAMLMapper.Builder yamlMapperBuilder = YAMLMapper.builder(yamlFactory);
yamlMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
yamlMapperBuilder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
if(yamlMapperCustomizer != null)
{
yamlMapperCustomizer.accept(yamlMapperBuilder);
}
YAMLMapper yamlMapper = yamlMapperBuilder.build();
yamlMapper.findAndRegisterModules();
return (yamlMapper.writeValueAsString(object));
}
catch(Exception e)
{
LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to yaml", e);
throw new IllegalArgumentException("Error in YAML Serialization", e);
}
}
}

View File

@ -31,8 +31,6 @@ import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -367,8 +365,6 @@ class MetaDataActionTest extends BaseTest
@Deprecated(since = "migrated to metaDataCustomizer")
void testFilter() throws QException
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
//////////////////////////////////////////////////////
// run default version, and assert tables are found //
//////////////////////////////////////////////////////
@ -379,7 +375,6 @@ class MetaDataActionTest extends BaseTest
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new MetaDataAction().execute(new MetaDataInput());
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("Using new default")).hasSize(1);
/////////////////////////////////////////////////////////////
// set up new instance to use a custom filter, to deny all //
@ -398,9 +393,6 @@ class MetaDataActionTest extends BaseTest
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new MetaDataAction().execute(new MetaDataInput());
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("actionCustomizer (via metaDataFilter reference) of type: DenyAllFilter")).hasSize(1);
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
////////////////////////////////////////////////////////////
// run now with the AllowAllFilter, confirm we get tables //
@ -420,8 +412,6 @@ class MetaDataActionTest extends BaseTest
@Test
void testCustomizer() throws QException
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
//////////////////////////////////////////////////////
// run default version, and assert tables are found //
//////////////////////////////////////////////////////
@ -432,7 +422,6 @@ class MetaDataActionTest extends BaseTest
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new MetaDataAction().execute(new MetaDataInput());
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("Using new default")).hasSize(1);
/////////////////////////////////////////////////////////////
// set up new instance to use a custom filter, to deny all //
@ -449,11 +438,9 @@ class MetaDataActionTest extends BaseTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
// mmm, we stopped loggin about it, so, we'll... assume the memoization is good //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new MetaDataAction().execute(new MetaDataInput());
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("meta-data actionCustomizer of type: DenyAllFilteringCustomizer")).hasSize(1);
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
/////////////////////////////////////////////////////////////////////////////////
// run now with the DefaultNoopMetaDataActionCustomizer, confirm we get tables //
@ -470,6 +457,7 @@ class MetaDataActionTest extends BaseTest
/***************************************************************************
**
***************************************************************************/
@SuppressWarnings("deprecation") // the point of this test is to use the deprecated thing.
public static class DenyAllFilter implements MetaDataFilterInterface
{
/***************************************************************************

View File

@ -366,7 +366,7 @@ public class RunProcessTest extends BaseTest
// only put back1 in the step list. //
//////////////////////////////////////
))
.addOptionalStep(front1)
.withOptionalStep(front1)
);
////////////////////////////////////////////////////////////

View File

@ -164,13 +164,13 @@ public class RunProcessUpdateStepListTest extends BaseTest
.withName(STEP_END)
));
process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_A));
process.addOptionalStep(new QBackendStepMetaData().withName(STEP_B).withCode(new QCodeReference(NoopBackendStep.class)));
process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_C));
process.withOptionalStep(new QFrontendStepMetaData().withName(STEP_A));
process.withOptionalStep(new QBackendStepMetaData().withName(STEP_B).withCode(new QCodeReference(NoopBackendStep.class)));
process.withOptionalStep(new QFrontendStepMetaData().withName(STEP_C));
process.addOptionalStep(new QBackendStepMetaData().withName(STEP_1).withCode(new QCodeReference(NoopBackendStep.class)));
process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_2));
process.addOptionalStep(new QBackendStepMetaData().withName(STEP_3).withCode(new QCodeReference(NoopBackendStep.class)));
process.withOptionalStep(new QBackendStepMetaData().withName(STEP_1).withCode(new QCodeReference(NoopBackendStep.class)));
process.withOptionalStep(new QFrontendStepMetaData().withName(STEP_2));
process.withOptionalStep(new QBackendStepMetaData().withName(STEP_3).withCode(new QCodeReference(NoopBackendStep.class)));
return (process);
}

View File

@ -576,7 +576,7 @@ class QInstanceEnricherTest extends BaseTest
QProcessMetaData process = new QProcessMetaData();
process.setName("test");
process.withStepList(List.of(new QBackendStepMetaData().withName("execute").withCode(new QCodeReference(TestUtils.IncreaseBirthdateStep.class))));
process.addOptionalStep(new QFrontendStepMetaData()
process.withOptionalStep(new QFrontendStepMetaData()
.withName("screen")
.withViewField(new QFieldMetaData("myField", QFieldType.STRING)));
qInstance.addProcess(process);

View File

@ -148,8 +148,10 @@ public class QInstanceValidatorTest extends BaseTest
/*******************************************************************************
**
* the point of this method is to test the deprecated member, so, don't need to
* get a compiler warning about usage of deprecated member.
*******************************************************************************/
@SuppressWarnings("deprecation")
@Test
void testMetaDataFilter()
{

View File

@ -370,11 +370,11 @@ public class TestUtils
.withName(PROCESS_NAME_INCREASE_BIRTHDATE)
.withTableName(TABLE_NAME_PERSON_MEMORY)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("preview")
)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("doWork")
.withCode(new QCodeReference(IncreaseBirthdateStep.class))
.withInputData(new QFunctionInputMetaData()
@ -383,7 +383,7 @@ public class TestUtils
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING).withDefaultValue("Success!"))))
)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("results")
.withFormField(new QFieldMetaData("outputMessage", QFieldType.STRING))
);
@ -1187,7 +1187,7 @@ public class TestUtils
return new QProcessMetaData()
.withName(PROCESS_NAME_GREET_PEOPLE)
.withTableName(TABLE_NAME_PERSON_MEMORY)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("prepare")
.withCode(new QCodeReference()
.withName(MockBackendStep.class.getName())
@ -1218,13 +1218,13 @@ public class TestUtils
.withName(PROCESS_NAME_GREET_PEOPLE_INTERACTIVE)
.withTableName(TABLE_NAME_PERSON)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("setup")
.withFormField(new QFieldMetaData("greetingPrefix", QFieldType.STRING))
.withFormField(new QFieldMetaData("greetingSuffix", QFieldType.STRING))
)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("doWork")
.withCode(new QCodeReference()
.withName(MockBackendStep.class.getName())
@ -1243,7 +1243,7 @@ public class TestUtils
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("results")
.withFormField(new QFieldMetaData("outputMessage", QFieldType.STRING))
);
@ -1264,7 +1264,7 @@ public class TestUtils
return new QProcessMetaData()
.withName(PROCESS_NAME_ADD_TO_PEOPLES_AGE)
.withTableName(TABLE_NAME_PERSON)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("getAgeStatistics")
.withCode(new QCodeReference()
.withName(GetAgeStatistics.class.getName())
@ -1278,7 +1278,7 @@ public class TestUtils
.withFieldList(List.of(
new QFieldMetaData("minAge", QFieldType.INTEGER),
new QFieldMetaData("maxAge", QFieldType.INTEGER)))))
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("addAge")
.withCode(new QCodeReference()
.withName(AddAge.class.getName())
@ -1308,7 +1308,7 @@ public class TestUtils
.withTableName(defineTableBasepull().getName()))
.withName(PROCESS_NAME_BASEPULL)
.withTableName(TABLE_NAME_PERSON)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("prepare")
.withCode(new QCodeReference()
.withName(MockBackendStep.class.getName())

View File

@ -24,9 +24,11 @@ package com.kingsrook.qqq.backend.core.utils;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
@ -60,6 +62,47 @@ class YamlUtilsTest extends BaseTest
/***************************************************************************
* simple bean to use in customObjectMapper test
* we'd use a map, but SORT_PROPERTIES_ALPHABETICALLY doesn't apply to maps...
***************************************************************************/
private record SomeBean(String foo, Integer bar) {}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testCustomObjectMapper() throws JsonProcessingException
{
SomeBean someBean = new SomeBean("Hi", 47);
///////////////////////////////////////////////////////////////////
// by default, the fields come out in the order they're declared //
///////////////////////////////////////////////////////////////////
assertEquals("""
foo: "Hi"
bar: 47
""", YamlUtils.toYaml(someBean));
/////////////////////////////////////////////////////////////
// customize the builder to sort properties alphabetically //
// (to assert that doing a customization works) //
/////////////////////////////////////////////////////////////
String outputYaml = YamlUtils.toYamlCustomized(someBean, yamlMapperBuilder ->
{
yamlMapperBuilder.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
});
assertEquals("""
bar: 47
foo: "Hi"
""", outputYaml);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer;
@ -71,6 +72,7 @@ import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
import com.kingsrook.qqq.backend.module.api.model.OutboundAPILog;
import com.kingsrook.qqq.backend.module.api.model.OutboundAPILogMetaDataProvider;
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendVariantSetting;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
@ -684,10 +686,11 @@ class BaseAPIActionUtilTest extends BaseTest
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
backend.setAuthorizationType(AuthorizationType.API_KEY_HEADER);
backend.setUsesVariants(true);
backend.setVariantOptionsTableName("variant");
backend.setVariantOptionsTableIdField("id");
backend.setVariantOptionsTableApiKeyField("apiKey");
backend.setVariantOptionsTableTypeValue("API_KEY_TYPE");
backend.setBackendVariantsConfig(new BackendVariantsConfig()
.withOptionsTableName("variant")
.withVariantTypeKey("API_KEY_TYPE")
.withBackendSettingSourceFieldName(APIBackendVariantSetting.API_KEY, "apiKey")
);
InsertInput insertInput = new InsertInput();
insertInput.setTableName("variant");
@ -716,11 +719,12 @@ class BaseAPIActionUtilTest extends BaseTest
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
backend.setAuthorizationType(AuthorizationType.BASIC_AUTH_USERNAME_PASSWORD);
backend.setUsesVariants(true);
backend.setVariantOptionsTableName("variant");
backend.setVariantOptionsTableIdField("id");
backend.setVariantOptionsTableUsernameField("username");
backend.setVariantOptionsTablePasswordField("password");
backend.setVariantOptionsTableTypeValue("USER_PASS");
backend.setBackendVariantsConfig(new BackendVariantsConfig()
.withOptionsTableName("variant")
.withVariantTypeKey("USER_PASS")
.withBackendSettingSourceFieldName(APIBackendVariantSetting.USERNAME, "username")
.withBackendSettingSourceFieldName(APIBackendVariantSetting.PASSWORD, "password")
);
InsertInput insertInput = new InsertInput();
insertInput.setTableName("variant");
@ -736,7 +740,7 @@ class BaseAPIActionUtilTest extends BaseTest
util.setBackendMetaData(backend);
util.setupAuthorizationInRequest(httpGet);
Header authHeader = httpGet.getFirstHeader("Authorization");
assertTrue(authHeader.getValue().equals(util.getBasicAuthenticationHeader("user", "pass")));
assertEquals(authHeader.getValue(), util.getBasicAuthenticationHeader("user", "pass"));
}

View File

@ -49,7 +49,7 @@ public class FilesystemImporterProcessMetaDataBuilder extends AbstractProcessMet
public FilesystemImporterProcessMetaDataBuilder()
{
super(new QProcessMetaData()
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("sync")
.withCode(new QCodeReference(FilesystemImporterStep.class))
.withInputData(new QFunctionInputMetaData()

View File

@ -76,6 +76,6 @@ public class FilesystemSyncProcess
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(syncStep);
.withStep(syncStep);
}
}

View File

@ -26,6 +26,7 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
@ -176,7 +177,7 @@ public class S3Utils
///////////////////////////////////////////
// skip files that do not match the glob //
///////////////////////////////////////////
if(!pathMatcher.matches(Path.of(URI.create("file:///" + URLEncoder.encode(key)))))
if(!pathMatcher.matches(Path.of(URI.create("file:///" + URLEncoder.encode(key, StandardCharsets.UTF_8)))))
{
// LOG.debug("Skipping file [{}] that does not match glob [{}]", key, glob);
continue;

View File

@ -183,7 +183,7 @@ public class BasicETLCleanupSourceFilesStepTest extends BaseTest
private RunBackendStepOutput runFunction(QInstance qInstance, List<String> filePaths, Map<String, String> values) throws Exception
{
QBackendStepMetaData backendStepMetaData = new BasicETLCleanupSourceFilesStep().defineStepMetaData();
QProcessMetaData qProcessMetaData = new QProcessMetaData().withName("testScaffold").addStep(backendStepMetaData);
QProcessMetaData qProcessMetaData = new QProcessMetaData().withName("testScaffold").withStep(backendStepMetaData);
qInstance.addProcess(qProcessMetaData);
HashSet<String> filePathsSet = new HashSet<>(filePaths);

View File

@ -96,7 +96,7 @@ class BasicETLCollectSourceFileNamesStepTest extends BaseTest
{
QInstance qInstance = TestUtils.defineInstance();
QBackendStepMetaData backendStepMetaData = new BasicETLCollectSourceFileNamesStep().defineStepMetaData();
QProcessMetaData qProcessMetaData = new QProcessMetaData().withName("testScaffold").addStep(backendStepMetaData);
QProcessMetaData qProcessMetaData = new QProcessMetaData().withName("testScaffold").withStep(backendStepMetaData);
qInstance.addProcess(qProcessMetaData);
reInitInstanceInContext(qInstance);

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.module.sqlite;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -86,7 +87,6 @@ public class TestUtils
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static void primeTestDatabase(String sqlFileName) throws Exception
{
SQLiteBackendMetaData backend = TestUtils.defineBackend();
@ -98,7 +98,7 @@ public class TestUtils
{
InputStream primeTestDatabaseSqlStream = SQLiteBackendModule.class.getResourceAsStream("/" + sqlFileName);
assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
List<String> lines = IOUtils.readLines(primeTestDatabaseSqlStream, StandardCharsets.UTF_8);
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
String joinedSQL = String.join("\n", lines);
for(String sql : joinedSQL.split(";"))

View File

@ -83,6 +83,7 @@ import com.kingsrook.qqq.openapi.model.Content;
import com.kingsrook.qqq.openapi.model.Example;
import com.kingsrook.qqq.openapi.model.ExampleWithListValue;
import com.kingsrook.qqq.openapi.model.ExampleWithSingleValue;
import com.kingsrook.qqq.openapi.model.In;
import com.kingsrook.qqq.openapi.model.Info;
import com.kingsrook.qqq.openapi.model.Method;
import com.kingsrook.qqq.openapi.model.OpenAPI;
@ -93,6 +94,7 @@ import com.kingsrook.qqq.openapi.model.Response;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.SecurityScheme;
import com.kingsrook.qqq.openapi.model.Tag;
import com.kingsrook.qqq.openapi.model.Type;
import io.javalin.http.ContentType;
import io.javalin.http.HttpStatus;
import org.apache.commons.lang.BooleanUtils;
@ -289,16 +291,16 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
*/
componentSchemas.put("baseSearchResultFields", new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(MapBuilder.of(
"count", new Schema()
.withType("integer")
.withType(Type.INTEGER)
.withDescription("Number of records that matched the search criteria"),
"pageNo", new Schema()
.withType("integer")
.withType(Type.INTEGER)
.withDescription("Requested result page number"),
"pageSize", new Schema()
.withType("integer")
.withType(Type.INTEGER)
.withDescription("Requested result page size")
)));
@ -440,11 +442,11 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
if(queryByQueryStringEnabled)
{
componentSchemas.put(tableApiName + "SearchResult", new Schema()
.withType("object")
.withType(Type.OBJECT)
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/baseSearchResultFields")))
.withProperties(MapBuilder.of(
"records", new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema()
.withAllOf(ListBuilder.of(
new Schema().withRef("#/components/schemas/" + tableApiName)))))));
@ -470,29 +472,29 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
new Parameter()
.withName("pageNo")
.withDescription("Which page of results to return. Starts at 1.")
.withIn("query")
.withSchema(new Schema().withType("integer")),
.withIn(In.QUERY)
.withSchema(new Schema().withType(Type.INTEGER)),
new Parameter()
.withName("pageSize")
.withDescription("Max number of records to include in a page. Defaults to 50. Must be between 1 and 1000.")
.withIn("query")
.withSchema(new Schema().withType("integer")),
.withIn(In.QUERY)
.withSchema(new Schema().withType(Type.INTEGER)),
new Parameter()
.withName("includeCount")
.withDescription("Whether or not to include the count (total matching records) in the result. Default is true.")
.withIn("query")
.withSchema(new Schema().withType("boolean").withEnumValues(ListBuilder.of("true", "false"))),
.withIn(In.QUERY)
.withSchema(new Schema().withType(Type.BOOLEAN).withEnumValues(ListBuilder.of("true", "false"))),
new Parameter()
.withName("orderBy")
.withDescription("How the results of the query should be sorted. SQL-style, comma-separated list of field names, each optionally followed by ASC or DESC (defaults to ASC).")
.withIn("query")
.withSchema(new Schema().withType("string"))
.withIn(In.QUERY)
.withSchema(new Schema().withType(Type.STRING))
.withExamples(buildOrderByExamples(apiName, primaryKeyApiName, tableApiFields)),
new Parameter()
.withName("booleanOperator")
.withDescription("Whether to combine query field as an AND or an OR. Default is AND.")
.withIn("query")
.withSchema(new Schema().withType("string").withEnumValues(ListBuilder.of("AND", "OR")))))
.withIn(In.QUERY)
.withSchema(new Schema().withType(Type.STRING).withEnumValues(ListBuilder.of("AND", "OR")))))
.withResponses(buildStandardErrorResponses(apiInstanceMetaData))
.withResponse(HttpStatus.OK.getCode(), new Response()
.withDescription("Successfully searched the " + tableLabel + " table (though may have found 0 records).")
@ -523,11 +525,11 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
queryGet.getParameters().add(new Parameter()
.withName(fieldName)
.withDescription(description.toString())
.withIn("query")
.withIn(In.QUERY)
.withExplode(true)
.withSchema(new Schema()
.withType("array")
.withItems(new Schema().withType("string")))
.withType(Type.ARRAY)
.withItems(new Schema().withType(Type.STRING)))
.withExamples(getCriteriaExamples(openAPI.getComponents().getExamples(), tableApiField)));
}
@ -553,7 +555,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
new Parameter()
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to get.")
.withIn("path")
.withIn(In.PATH)
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))))
.withResponses(buildStandardErrorResponses(apiInstanceMetaData))
@ -573,7 +575,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
new Parameter()
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to update.")
.withIn("path")
.withIn(In.PATH)
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))))
.withRequestBody(new RequestBody()
@ -595,7 +597,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
new Parameter()
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to delete.")
.withIn("path")
.withIn(In.PATH)
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))))
.withResponses(buildStandardErrorResponses(apiInstanceMetaData))
@ -627,7 +629,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription("Successfully created the requested " + tableLabel)
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(MapBuilder.of(primaryKeyApiName, new Schema()
.withType(getFieldType(primaryKeyField))
.withExample("47")))))))
@ -652,7 +654,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription("Values for the " + tableLabel + " records to create.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema().withRef("#/components/schemas/" + tableApiName))))))
.withResponses(buildStandardErrorResponses(apiInstanceMetaData))
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyApiName, primaryKeyField, "post"))
@ -668,7 +670,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription("Values for the " + tableLabel + " records to update.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema()
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableApiName)))
.withProperties(MapBuilder.of(primaryKeyApiName, new Schema()
@ -690,7 +692,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription(primaryKeyLabel + " values for the " + tableLabel + " records to delete.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema().withType(getFieldType(primaryKeyField)))
.withExample(List.of(42, 47))))))
.withResponses(buildStandardErrorResponses(apiInstanceMetaData))
@ -910,7 +912,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
for(QFieldMetaData field : CollectionUtils.nonNullList(pathParams.getFields()))
{
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn("path"));
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn(In.PATH));
}
}
@ -922,14 +924,14 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
if(queryStringParams.getRecordIdsField() != null)
{
parameters.add(processFieldToParameter(apiInstanceMetaData, queryStringParams.getRecordIdsField()).withIn("query"));
parameters.add(processFieldToParameter(apiInstanceMetaData, queryStringParams.getRecordIdsField()).withIn(In.QUERY));
}
for(QFieldMetaData field : CollectionUtils.nonNullList(queryStringParams.getFields()))
{
if(ApiFieldUtils.isIncluded(apiName, field) && ApiFieldUtils.getApiVersionRange(apiName, field).includes(apiVersion))
{
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn("query"));
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn(In.QUERY));
}
}
}
@ -961,7 +963,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
content.withSchema(new Schema()
.withDescription(bodyDescription)
.withType("string")
.withType(Type.STRING)
.withExample(exampleWithSingleValue.getValue()));
}
@ -982,7 +984,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
parameters.add(new Parameter()
.withName("async")
.withIn("query")
.withIn(In.QUERY)
.withDescription("""
Indicates if the job should be ran asynchronously.
If false or not specified, then the job is ran synchronously and returns with an appropriate response status when completed.
@ -993,7 +995,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
"false", new ExampleWithSingleValue().withValue(false).withSummary("Run the job synchronously."),
"true", new ExampleWithSingleValue().withValue(true).withSummary("Run the job asynchronously.")
))
.withSchema(new Schema().withType("boolean")));
.withSchema(new Schema().withType(Type.BOOLEAN)));
}
if(CollectionUtils.nullSafeHasContents(parameters))
@ -1018,9 +1020,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription("The process has been started asynchronously. You can call back later to check its status.")
.withContent(MapBuilder.of(ContentType.JSON, new Content()
.withSchema(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(MapBuilder.of(
"jobId", new Schema().withType("string").withFormat("uuid").withDescription("id of the asynchronous job")
"jobId", new Schema().withType(Type.STRING).withFormat("uuid").withDescription("id of the asynchronous job")
))
)
))
@ -1076,16 +1078,16 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
for(QFieldMetaData field : CollectionUtils.nonNullList(pathParams.getFields()))
{
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn("path"));
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn(In.PATH));
}
}
parameters.add(new Parameter()
.withName("jobId")
.withIn("path")
.withIn(In.PATH)
.withRequired(true)
.withDescription("Id of the job, as returned by the API call that started it.")
.withSchema(new Schema().withType("string").withFormat("uuid")));
.withSchema(new Schema().withType(Type.STRING).withFormat("uuid")));
////////////////////////////////////////////////////////
// add the async input for optionally-async processes //
@ -1100,12 +1102,12 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription("The process is still running. You can call back later to get its final status.")
.withContent(MapBuilder.of(ContentType.JSON, new Content()
.withSchema(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(MapBuilder.of(
"jobId", new Schema().withType("string").withFormat("uuid").withDescription("id of the asynchronous job"),
"message", new Schema().withNullable(true).withType("string").withDescription("a status message about the progress of the job").withExample("Processing records"),
"current", new Schema().withNullable(true).withType("integer").withDescription("for jobs that count progress, indicator of the current number being processed").withExample(7),
"total", new Schema().withNullable(true).withType("integer").withDescription("for jobs that count progress, indicator of the total number being processed").withExample(9)
"jobId", new Schema().withType(Type.STRING).withFormat("uuid").withDescription("id of the asynchronous job"),
"message", new Schema().withNullable(true).withType(Type.STRING).withDescription("a status message about the progress of the job").withExample("Processing records"),
"current", new Schema().withNullable(true).withType(Type.INTEGER).withDescription("for jobs that count progress, indicator of the current number being processed").withExample(7),
"total", new Schema().withNullable(true).withType(Type.INTEGER).withDescription("for jobs that count progress, indicator of the total number being processed").withExample(9)
))
)
))
@ -1268,7 +1270,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
{
LinkedHashMap<String, Schema> tableFields = new LinkedHashMap<>();
Schema tableSchema = new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(tableFields);
for(QFieldMetaData field : tableApiFields)
@ -1461,7 +1463,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
neededTableSchemas.add(associatedTable.getName());
tableSchema.getProperties().put(association.getName(), new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema().withRef("#/components/schemas/" + associatedTableApiName)));
}
}
@ -1607,18 +1609,18 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
};
Map<String, Schema> properties = new LinkedHashMap<>();
properties.put("statusCode", new Schema().withType("integer"));
properties.put("statusText", new Schema().withType("string"));
properties.put("error", new Schema().withType("string"));
properties.put("statusCode", new Schema().withType(Type.INTEGER));
properties.put("statusText", new Schema().withType(Type.STRING));
properties.put("error", new Schema().withType(Type.STRING));
properties.put(primaryKeyApiName, new Schema().withType(getFieldType(primaryKeyField)));
return new Response()
.withDescription("Multiple statuses. See body for details.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(properties))
.withExample(example))));
}
@ -1704,7 +1706,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
public static String getFieldType(QFieldMetaData field)
public static Type getFieldType(QFieldMetaData field)
{
return (getFieldType(field.getType()));
}
@ -1714,14 +1716,14 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private static String getFieldType(QFieldType type)
private static Type getFieldType(QFieldType type)
{
return switch(type)
{
case STRING, DATE, TIME, DATE_TIME, TEXT, HTML, PASSWORD, BLOB -> "string";
case INTEGER, LONG -> "integer"; // todo - we could give 'format' w/ int32 & int64 to further specify
case DECIMAL -> "number";
case BOOLEAN -> "boolean";
case STRING, DATE, TIME, DATE_TIME, TEXT, HTML, PASSWORD, BLOB -> Type.STRING;
case INTEGER, LONG -> Type.INTEGER; // todo - we could give 'format' w/ int32 & int64 to further specify
case DECIMAL -> Type.NUMBER;
case BOOLEAN -> Type.BOOLEAN;
};
}
@ -1796,9 +1798,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withDescription(description)
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(MapBuilder.of("error", new Schema()
.withType("string")
.withType(Type.STRING)
.withExample(example)
))
)

View File

@ -183,7 +183,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
}
}
fields.sort(Comparator.comparing(QFieldMetaData::getLabel));
fields.sort(Comparator.comparing(QFieldMetaData::getLabel).thenComparing(QFieldMetaData::getName));
return (new GetTableApiFieldsOutput().withFields(fields));
}

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
import com.kingsrook.qqq.openapi.model.Content;
import com.kingsrook.qqq.openapi.model.Response;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.Type;
import org.eclipse.jetty.http.HttpStatus;
@ -113,7 +114,7 @@ public class RenderSavedReportProcessApiProcessOutput implements ApiProcessOutpu
{"id": 2, "name": "Jean-Luc"}
]
""")
.withType("string")
.withType(Type.STRING)
.withFormat("text")));
contentMap.put(ReportFormat.CSV.getMimeType(), new Content()
@ -124,13 +125,13 @@ public class RenderSavedReportProcessApiProcessOutput implements ApiProcessOutpu
1,"James"
2,"Jean-Luc"
""")
.withType("string")
.withType(Type.STRING)
.withFormat("text")));
contentMap.put(ReportFormat.XLSX.getMimeType(), new Content()
.withSchema(new Schema()
.withDescription("Excel Report contents")
.withType("string")
.withType(Type.STRING)
.withFormat("binary")));
return Map.of(HttpStatus.Code.OK.getCode(), new Response()

View File

@ -43,6 +43,7 @@ import com.kingsrook.qqq.openapi.model.ExampleWithListValue;
import com.kingsrook.qqq.openapi.model.ExampleWithSingleValue;
import com.kingsrook.qqq.openapi.model.Response;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.Type;
import io.javalin.http.ContentType;
import org.eclipse.jetty.http.HttpStatus;
@ -109,7 +110,7 @@ public class ApiProcessObjectOutput implements ApiProcessOutputInterface
.withDescription(ObjectUtils.requireConditionElse(responseDescription, StringUtils::hasContent, "Process has been successfully executed."))
.withContent(MapBuilder.of(ContentType.JSON, new Content()
.withSchema(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(properties))))
));
}

View File

@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import com.kingsrook.qqq.openapi.model.Content;
import com.kingsrook.qqq.openapi.model.Response;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.Type;
import io.javalin.http.ContentType;
import org.apache.commons.lang.NotImplementedException;
import org.eclipse.jetty.http.HttpStatus;
@ -89,10 +90,10 @@ public class ApiProcessSummaryListOutput implements ApiProcessOutputInterface
public Map<Integer, Response> getSpecResponses(String apiName)
{
Map<String, Schema> propertiesFor207Object = new LinkedHashMap<>();
propertiesFor207Object.put("id", new Schema().withType("integer").withDescription("Id of the record whose status is being described in the object"));
propertiesFor207Object.put("statusCode", new Schema().withType("integer").withDescription("HTTP Status code indicating the success or failure of the process on this record"));
propertiesFor207Object.put("statusText", new Schema().withType("string").withDescription("HTTP Status text indicating the success or failure of the process on this record"));
propertiesFor207Object.put("message", new Schema().withType("string").withDescription("Additional descriptive information about the result of the process on this record."));
propertiesFor207Object.put("id", new Schema().withType(Type.INTEGER).withDescription("Id of the record whose status is being described in the object"));
propertiesFor207Object.put("statusCode", new Schema().withType(Type.INTEGER).withDescription("HTTP Status code indicating the success or failure of the process on this record"));
propertiesFor207Object.put("statusText", new Schema().withType(Type.STRING).withDescription("HTTP Status text indicating the success or failure of the process on this record"));
propertiesFor207Object.put("message", new Schema().withType(Type.STRING).withDescription("Additional descriptive information about the result of the process on this record."));
List<Object> exampleFor207Object = ListBuilder.of(MapBuilder.of(LinkedHashMap::new)
.with("id", 42)
@ -112,9 +113,9 @@ public class ApiProcessSummaryListOutput implements ApiProcessOutputInterface
.withDescription("For each input record, an object describing its status may be returned.")
.withContent(MapBuilder.of(ContentType.JSON, new Content()
.withSchema(new Schema()
.withType("array")
.withType(Type.ARRAY)
.withItems(new Schema()
.withType("object")
.withType(Type.OBJECT)
.withProperties(propertiesFor207Object))
.withExample(exampleFor207Object)
)

View File

@ -219,7 +219,7 @@ public class TestUtils
.withName(PROCESS_NAME_GET_PERSON_INFO)
.withLabel("Get Person Info")
.withTableName(TABLE_NAME_PERSON)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("enterInputs")
.withLabel("Person Info Input")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
@ -245,11 +245,11 @@ public class TestUtils
"""))
))
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("execute")
.withCode(new QCodeReference(GetPersonInfoStep.class)))
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("dummyStep")
);

View File

@ -104,7 +104,7 @@ public class PublishAPI implements Callable<Integer>
// subsets of it (e.g., grouped by table mostly) - then we'll write out each such file //
/////////////////////////////////////////////////////////////////////////////////////////////////
OpenAPI openAPI = middlewareVersion.generateOpenAPIModel("qqq");
String yaml = YamlUtils.toYaml(openAPI, mapper ->
String yaml = YamlUtils.toYamlCustomized(openAPI, mapperBuilder ->
{
if(sortFileContentsForHuman)
{
@ -114,7 +114,7 @@ public class PublishAPI implements Callable<Integer>
}
else
{
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
mapperBuilder.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
}
});

View File

@ -112,9 +112,9 @@ public class ValidateAPIVersions implements Callable<Integer>
// generate a new spec based on current code in codebase //
///////////////////////////////////////////////////////////
OpenAPI openAPI = middlewareVersion.generateOpenAPIModel("qqq");
String yaml = YamlUtils.toYaml(openAPI, mapper ->
String yaml = YamlUtils.toYamlCustomized(openAPI, mapperBuilder ->
{
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
mapperBuilder.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
});
/////////////////////////////////////////////////////////////////////

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.javalin;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
@ -123,7 +124,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
.withValues(List.of(3, 4, 5)));
String filterJSON = JsonUtils.toJson(queryFilter);
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(filterJSON, Charset.defaultCharset())).asString();
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8)).asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);

View File

@ -169,7 +169,7 @@ public class TestUtils
return new QProcessMetaData()
.withName("greet")
.withTableName("person")
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("prepare")
.withCode(new QCodeReference()
.withName(MockBackendStep.class.getName())