mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
Merged dev into feature/rdbms-connection-pool
This commit is contained in:
@ -32,7 +32,9 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -77,6 +79,7 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
|
||||
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.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
@ -100,6 +103,7 @@ import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSo
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
|
||||
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.fields.AdornmentType;
|
||||
@ -107,6 +111,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendVariant;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
@ -279,6 +284,14 @@ public class QJavalinImplementation
|
||||
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// clear the cache of classes in this class, so that new classes can be found //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
MetaDataProducerHelper.clearTopLevelClassCache();
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// try to get a new instance from the supplier //
|
||||
/////////////////////////////////////////////////
|
||||
QInstance newQInstance = qInstanceHotSwapSupplier.get();
|
||||
if(newQInstance == null)
|
||||
{
|
||||
@ -286,6 +299,9 @@ public class QJavalinImplementation
|
||||
return;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// validate the instance, and only if it passes, then set it in our static field //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
new QInstanceValidator().validate(newQInstance);
|
||||
QJavalinImplementation.qInstance = newQInstance;
|
||||
LOG.info("Swapped qInstance");
|
||||
@ -364,8 +380,8 @@ public class QJavalinImplementation
|
||||
post("/export", QJavalinImplementation::dataExportWithoutFilename);
|
||||
get("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
|
||||
post("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
|
||||
get("/possibleValues/{fieldName}", QJavalinImplementation::possibleValues);
|
||||
post("/possibleValues/{fieldName}", QJavalinImplementation::possibleValues);
|
||||
get("/possibleValues/{fieldName}", QJavalinImplementation::possibleValuesForTableField);
|
||||
post("/possibleValues/{fieldName}", QJavalinImplementation::possibleValuesForTableField);
|
||||
|
||||
// todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records.
|
||||
path("/{primaryKey}", () ->
|
||||
@ -382,6 +398,9 @@ public class QJavalinImplementation
|
||||
});
|
||||
});
|
||||
|
||||
get("/possibleValues/{possibleValueSourceName}", QJavalinImplementation::possibleValuesStandalone);
|
||||
post("/possibleValues/{possibleValueSourceName}", QJavalinImplementation::possibleValuesStandalone);
|
||||
|
||||
get("/widget/{name}", QJavalinImplementation::widget); // todo - can we just do a slow log here?
|
||||
|
||||
get("/serverInfo", QJavalinImplementation::serverInfo);
|
||||
@ -427,7 +446,17 @@ public class QJavalinImplementation
|
||||
QSession session = authenticationModule.createSession(qInstance, authContext);
|
||||
|
||||
context.cookie(SESSION_UUID_COOKIE_NAME, session.getUuid(), SESSION_COOKIE_AGE);
|
||||
context.result(JsonUtils.toJson(MapBuilder.of("uuid", session.getUuid())));
|
||||
|
||||
Map<String, Serializable> resultMap = new HashMap<>();
|
||||
resultMap.put("uuid", session.getUuid());
|
||||
|
||||
if(session.getValuesForFrontend() != null)
|
||||
{
|
||||
LinkedHashMap<String, Serializable> valuesForFrontend = new LinkedHashMap<>(session.getValuesForFrontend());
|
||||
resultMap.put("values", valuesForFrontend);
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(resultMap));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -710,10 +739,15 @@ public class QJavalinImplementation
|
||||
{
|
||||
throw (new QUserFacingException("Error updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getErrors())));
|
||||
}
|
||||
if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
|
||||
{
|
||||
throw (new QUserFacingException("Warning updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// at one time, we threw upon warning - but //
|
||||
// on insert we need to return the record (e.g., to get a generated id), so, make update do the same. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
|
||||
// {
|
||||
// throw (new QUserFacingException("Warning updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
|
||||
// }
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.result(JsonUtils.toJson(updateOutput));
|
||||
@ -779,9 +813,34 @@ public class QJavalinImplementation
|
||||
{
|
||||
String fieldName = formParam.getKey();
|
||||
List<String> values = formParam.getValue();
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
String value = values.get(0);
|
||||
|
||||
if("associations".equals(fieldName) && StringUtils.hasContent(value))
|
||||
{
|
||||
JSONObject associationsJSON = new JSONObject(value);
|
||||
for(String key : associationsJSON.keySet())
|
||||
{
|
||||
JSONArray associatedRecordsJSON = associationsJSON.getJSONArray(key);
|
||||
List<QRecord> associatedRecords = new ArrayList<>();
|
||||
record.withAssociatedRecords(key, associatedRecords);
|
||||
|
||||
for(int i = 0; i < associatedRecordsJSON.length(); i++)
|
||||
{
|
||||
QRecord associatedRecord = new QRecord();
|
||||
JSONObject recordJSON = associatedRecordsJSON.getJSONObject(i);
|
||||
for(String k : recordJSON.keySet())
|
||||
{
|
||||
associatedRecord.withValue(k, ValueUtils.getValueAsString(recordJSON.get(k)));
|
||||
}
|
||||
associatedRecords.add(associatedRecord);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(value))
|
||||
{
|
||||
record.setValue(fieldName, value);
|
||||
@ -793,7 +852,6 @@ public class QJavalinImplementation
|
||||
}
|
||||
else
|
||||
{
|
||||
// is this ever hit?
|
||||
record.setValue(fieldName, null);
|
||||
}
|
||||
}
|
||||
@ -881,10 +939,16 @@ public class QJavalinImplementation
|
||||
{
|
||||
throw (new QUserFacingException("Error inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getErrors())));
|
||||
}
|
||||
if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
|
||||
{
|
||||
throw (new QUserFacingException("Warning inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// at one time, we threw upon warning - but //
|
||||
// our use-case is, the frontend, it wants to get the record, and show a success (with the generated id) //
|
||||
// and then to also show a warning message - so - let it all be returned and handled on the frontend. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
|
||||
// {
|
||||
// throw (new QUserFacingException("Warning inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
|
||||
// }
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess(logPair("primaryKey", () -> (outputRecord.getValue(table.getPrimaryKeyField()))));
|
||||
context.result(JsonUtils.toJson(insertOutput));
|
||||
@ -1196,7 +1260,9 @@ public class QJavalinImplementation
|
||||
}
|
||||
if(filter != null)
|
||||
{
|
||||
queryInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||
QQueryFilter qQueryFilter = JsonUtils.toObject(filter, QQueryFilter.class);
|
||||
queryInput.setFilter(qQueryFilter);
|
||||
qQueryFilter.interpretValues(Collections.emptyMap());
|
||||
}
|
||||
|
||||
Integer skip = QJavalinUtils.integerQueryParam(context, "skip");
|
||||
@ -1460,10 +1526,11 @@ public class QJavalinImplementation
|
||||
setupSession(context, exportInput);
|
||||
|
||||
exportInput.setTableName(tableName);
|
||||
exportInput.setReportFormat(reportFormat);
|
||||
|
||||
String filename = optionalFilename.orElse(tableName + "." + reportFormat.toString().toLowerCase(Locale.ROOT));
|
||||
exportInput.setFilename(filename);
|
||||
exportInput.withReportDestination(new ReportDestination()
|
||||
.withReportFormat(reportFormat)
|
||||
.withFilename(filename));
|
||||
|
||||
Integer limit = QJavalinUtils.integerQueryParam(context, "limit");
|
||||
exportInput.setLimit(limit);
|
||||
@ -1494,7 +1561,7 @@ public class QJavalinImplementation
|
||||
|
||||
UnsafeFunction<PipedOutputStream, ExportAction, Exception> preAction = (PipedOutputStream pos) ->
|
||||
{
|
||||
exportInput.setReportOutputStream(pos);
|
||||
exportInput.getReportDestination().setReportOutputStream(pos);
|
||||
|
||||
ExportAction exportAction = new ExportAction();
|
||||
exportAction.preExecute(exportInput);
|
||||
@ -1650,9 +1717,9 @@ public class QJavalinImplementation
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** handler for a PVS that's associated with a field on a table.
|
||||
*******************************************************************************/
|
||||
private static void possibleValues(Context context)
|
||||
private static void possibleValuesForTableField(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1691,36 +1758,71 @@ public class QJavalinImplementation
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** handler for a standalone (e.g., outside of a table or process) PVS.
|
||||
*******************************************************************************/
|
||||
private static void possibleValuesStandalone(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
String possibleValueSourceName = context.pathParam("possibleValueSourceName");
|
||||
|
||||
QPossibleValueSource pvs = qInstance.getPossibleValueSource(possibleValueSourceName);
|
||||
if(pvs == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find possible value source " + possibleValueSourceName + " in this instance."));
|
||||
}
|
||||
|
||||
finishPossibleValuesRequest(context, possibleValueSourceName, null);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** continuation for table or process PVS's,
|
||||
*******************************************************************************/
|
||||
static void finishPossibleValuesRequest(Context context, QFieldMetaData field) throws IOException, QException
|
||||
{
|
||||
QQueryFilter defaultQueryFilter = null;
|
||||
if(field.getPossibleValueSourceFilter() != null)
|
||||
{
|
||||
Map<String, Serializable> values = new HashMap<>();
|
||||
if(context.formParamMap().containsKey("values"))
|
||||
{
|
||||
List<String> valuesParamList = context.formParamMap().get("values");
|
||||
if(CollectionUtils.nullSafeHasContents(valuesParamList))
|
||||
{
|
||||
String valuesParam = valuesParamList.get(0);
|
||||
values = JsonUtils.toObject(valuesParam, Map.class);
|
||||
}
|
||||
}
|
||||
|
||||
defaultQueryFilter = field.getPossibleValueSourceFilter().clone();
|
||||
defaultQueryFilter.interpretValues(values);
|
||||
}
|
||||
|
||||
finishPossibleValuesRequest(context, field.getPossibleValueSourceName(), defaultQueryFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void finishPossibleValuesRequest(Context context, String possibleValueSourceName, QQueryFilter defaultFilter) throws IOException, QException
|
||||
{
|
||||
String searchTerm = context.queryParam("searchTerm");
|
||||
String ids = context.queryParam("ids");
|
||||
|
||||
Map<String, Serializable> values = new HashMap<>();
|
||||
if(context.formParamMap().containsKey("values"))
|
||||
{
|
||||
List<String> valuesParamList = context.formParamMap().get("values");
|
||||
if(CollectionUtils.nullSafeHasContents(valuesParamList))
|
||||
{
|
||||
String valuesParam = valuesParamList.get(0);
|
||||
values = JsonUtils.toObject(valuesParam, Map.class);
|
||||
}
|
||||
}
|
||||
|
||||
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput();
|
||||
setupSession(context, input);
|
||||
input.setPossibleValueSourceName(field.getPossibleValueSourceName());
|
||||
input.setPossibleValueSourceName(possibleValueSourceName);
|
||||
input.setSearchTerm(searchTerm);
|
||||
|
||||
if(field.getPossibleValueSourceFilter() != null)
|
||||
{
|
||||
QQueryFilter filter = field.getPossibleValueSourceFilter().clone();
|
||||
filter.interpretValues(values);
|
||||
input.setDefaultQueryFilter(filter);
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(ids))
|
||||
{
|
||||
List<Serializable> idList = new ArrayList<>(Arrays.asList(ids.split(",")));
|
||||
@ -1732,6 +1834,7 @@ public class QJavalinImplementation
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("options", output.getResults());
|
||||
context.result(JsonUtils.toJson(result));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1876,4 +1979,13 @@ public class QJavalinImplementation
|
||||
MILLIS_BETWEEN_HOT_SWAPS = millisBetweenHotSwaps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static long getStartTimeMillis()
|
||||
{
|
||||
return (startTime);
|
||||
}
|
||||
}
|
||||
|
@ -45,10 +45,12 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
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.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
@ -60,15 +62,18 @@ 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;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
||||
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.fields.QFieldMetaData;
|
||||
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.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
@ -126,6 +131,7 @@ public class QJavalinProcessHandler
|
||||
post("/step/{step}", QJavalinProcessHandler::processStep);
|
||||
get("/status/{jobUUID}", QJavalinProcessHandler::processStatus);
|
||||
get("/records", QJavalinProcessHandler::processRecords);
|
||||
get("/cancel", QJavalinProcessHandler::processCancel);
|
||||
});
|
||||
|
||||
get("/possibleValues/{fieldName}", QJavalinProcessHandler::possibleValues);
|
||||
@ -203,10 +209,12 @@ public class QJavalinProcessHandler
|
||||
QJavalinImplementation.setupSession(context, reportInput);
|
||||
PermissionsHelper.checkReportPermissionThrowing(reportInput, reportName);
|
||||
|
||||
reportInput.setReportFormat(reportFormat);
|
||||
reportInput.setReportName(reportName);
|
||||
reportInput.setInputValues(null); // todo!
|
||||
reportInput.setFilename(filename);
|
||||
|
||||
reportInput.setReportDestination(new ReportDestination()
|
||||
.withReportFormat(reportFormat)
|
||||
.withFilename(filename));
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// process the report's input fields, from the query string //
|
||||
@ -239,7 +247,7 @@ public class QJavalinProcessHandler
|
||||
|
||||
UnsafeFunction<PipedOutputStream, GenerateReportAction, Exception> preAction = (PipedOutputStream pos) ->
|
||||
{
|
||||
reportInput.setReportOutputStream(pos);
|
||||
reportInput.getReportDestination().setReportOutputStream(pos);
|
||||
|
||||
GenerateReportAction reportAction = new GenerateReportAction();
|
||||
// any pre-action?? export uses this for "too many rows" checks...
|
||||
@ -282,12 +290,24 @@ public class QJavalinProcessHandler
|
||||
// todo context.contentType(reportFormat.getMimeType());
|
||||
context.header("Content-Disposition", "filename=" + context.pathParam("file"));
|
||||
|
||||
String filePath = context.queryParam("filePath");
|
||||
if(filePath == null)
|
||||
String filePath = context.queryParam("filePath");
|
||||
String storageTableName = context.queryParam("storageTableName");
|
||||
String reference = context.queryParam("storageReference");
|
||||
|
||||
if(filePath != null)
|
||||
{
|
||||
throw (new QBadRequestException("A filePath was not provided."));
|
||||
context.result(new FileInputStream(filePath));
|
||||
}
|
||||
context.result(new FileInputStream(filePath));
|
||||
else if(storageTableName != null && reference != null)
|
||||
{
|
||||
InputStream inputStream = new StorageAction().getInputStream(new StorageInput(storageTableName).withReference(reference));
|
||||
context.result(inputStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QBadRequestException("Missing query parameters to identify file to download"));
|
||||
}
|
||||
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -431,6 +451,12 @@ public class QJavalinProcessHandler
|
||||
}
|
||||
resultForCaller.put("values", runProcessOutput.getValues());
|
||||
runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
|
||||
|
||||
List<QFrontendStepMetaData> updatedFrontendStepList = runProcessOutput.getUpdatedFrontendStepList();
|
||||
if(updatedFrontendStepList != null)
|
||||
{
|
||||
resultForCaller.put("updatedFrontendStepList", updatedFrontendStepList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -744,6 +770,32 @@ public class QJavalinProcessHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processCancel(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
QJavalinImplementation.setupSession(context, runProcessInput);
|
||||
|
||||
runProcessInput.setProcessName(context.pathParam("processName"));
|
||||
runProcessInput.setProcessUUID(context.pathParam("processUUID"));
|
||||
|
||||
new CancelProcessAction().execute(runProcessInput);
|
||||
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinImplementation.handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import kong.unirest.HttpResponse;
|
||||
@ -514,6 +515,42 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test an insert that returns a warning
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_dataInsertWithWarning()
|
||||
{
|
||||
Map<String, Serializable> body = new HashMap<>();
|
||||
body.put("firstName", "Warning");
|
||||
body.put("lastName", "Kelkhoff");
|
||||
body.put("email", "warning@kelkhoff.com");
|
||||
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertTrue(jsonObject.has("records"));
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertEquals(1, records.length());
|
||||
JSONObject record0 = records.getJSONObject(0);
|
||||
assertTrue(record0.has("values"));
|
||||
JSONObject values0 = record0.getJSONObject("values");
|
||||
assertTrue(values0.has("id"));
|
||||
assertEquals(7, values0.getInt("id"));
|
||||
|
||||
assertTrue(record0.has("warnings"));
|
||||
JSONArray warnings = record0.getJSONArray("warnings");
|
||||
assertEquals(1, warnings.length());
|
||||
assertTrue(warnings.getJSONObject(0).has("message"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test an insert - posting a multipart form.
|
||||
**
|
||||
@ -594,6 +631,52 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test an update - with a warning returned
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_dataUpdateWithWarning()
|
||||
{
|
||||
Map<String, Serializable> body = new HashMap<>();
|
||||
body.put("firstName", "Warning");
|
||||
body.put("birthDate", "");
|
||||
|
||||
HttpResponse<String> response = Unirest.patch(BASE_URL + "/data/person/4")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertTrue(jsonObject.has("records"));
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertEquals(1, records.length());
|
||||
JSONObject record0 = records.getJSONObject(0);
|
||||
assertTrue(record0.has("values"));
|
||||
assertEquals("person", record0.getString("tableName"));
|
||||
JSONObject values0 = record0.getJSONObject("values");
|
||||
assertEquals(4, values0.getInt("id"));
|
||||
assertEquals("Warning", values0.getString("firstName"));
|
||||
|
||||
assertTrue(record0.has("warnings"));
|
||||
JSONArray warnings = record0.getJSONArray("warnings");
|
||||
assertEquals(1, warnings.length());
|
||||
assertTrue(warnings.getJSONObject(0).has("message"));
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// re-GET the record, and validate that birthDate was nulled out //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
response = Unirest.get(BASE_URL + "/data/person/4").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertTrue(jsonObject.has("values"));
|
||||
JSONObject values = jsonObject.getJSONObject("values");
|
||||
assertFalse(values.has("birthDate"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test an update - posting the data as a multipart form
|
||||
**
|
||||
@ -761,6 +844,24 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPossibleValueWithoutTableOrProcess()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/possibleValues/person").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertNotNull(jsonObject.getJSONArray("options"));
|
||||
assertEquals(6, jsonObject.getJSONArray("options").length());
|
||||
assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
|
||||
assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -882,7 +983,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
|
||||
Function<String, QInstance> makeNewInstanceWithBackendName = (backendName) ->
|
||||
{
|
||||
QInstance newInstance = new QInstance();
|
||||
newInstance.addBackend(new QBackendMetaData().withName(backendName).withBackendType("mock"));
|
||||
newInstance.addBackend(new QBackendMetaData().withName(backendName).withBackendType(MockBackendModule.class));
|
||||
|
||||
if(!"invalid".equals(backendName))
|
||||
{
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.javalin;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
@ -575,15 +576,15 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test calling download file with missing filePath
|
||||
** test calling download file without needed query-string params
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_downloadFileMissingFilePath()
|
||||
public void test_downloadFileMissingQueryStringParams()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/download/myTestFile.txt").asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
assertTrue(response.getBody().contains("A filePath was not provided"));
|
||||
assertTrue(response.getBody().contains("Missing query parameters to identify file"));
|
||||
}
|
||||
|
||||
|
||||
@ -604,4 +605,45 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
|
||||
assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
|
||||
assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test_processCancel()
|
||||
{
|
||||
/////////////////////////
|
||||
// 400s for bad inputs //
|
||||
/////////////////////////
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/noSuchProcess/" + UUID.randomUUID() + "/cancel").asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
assertThat(response.getBody()).contains("Process [noSuchProcess] is not defined in this instance");
|
||||
}
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/" + UUID.randomUUID() + "/cancel").asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
assertThat(response.getBody()).matches(".*State for process UUID.*not found.*");
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// start a process, get its uuid //
|
||||
///////////////////////////////////
|
||||
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SLEEP_INTERACTIVE;
|
||||
HttpResponse<String> response = Unirest.post(processBasePath + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
assertNotNull(processUUID, "Process UUID should not be null.");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// now cancel that, and expect success //
|
||||
/////////////////////////////////////////
|
||||
response = Unirest.get(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SLEEP_INTERACTIVE + "/" + processUUID + "/cancel").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,10 @@ package com.kingsrook.qqq.backend.javalin;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -35,6 +39,7 @@ 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;
|
||||
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.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
@ -68,6 +73,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedViewsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
@ -225,7 +232,7 @@ public class TestUtils
|
||||
public static QBackendMetaData defineMemoryBackend()
|
||||
{
|
||||
return new QBackendMetaData()
|
||||
.withBackendType("memory")
|
||||
.withBackendType(MemoryBackendModule.class)
|
||||
.withName(BACKEND_NAME_MEMORY);
|
||||
}
|
||||
|
||||
@ -260,6 +267,9 @@ public class TestUtils
|
||||
.withScriptTypeId(1)
|
||||
.withScriptTester(new QCodeReference(TestScriptAction.class)));
|
||||
|
||||
qTableMetaData.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PersonTableCustomizer.class));
|
||||
qTableMetaData.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PersonTableCustomizer.class));
|
||||
|
||||
qTableMetaData.getField("photo")
|
||||
.withIsHeavy(true)
|
||||
.withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD)
|
||||
@ -271,6 +281,52 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PersonTableCustomizer implements TableCustomizerInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
return warnPostInsertOrUpdate(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
return warnPostInsertOrUpdate(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> warnPostInsertOrUpdate(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(Objects.requireNonNullElse(record.getValueString("firstName"), "").toLowerCase().contains("warn"))
|
||||
{
|
||||
record.addWarning(new QWarningMessage("Warning in firstName."));
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user