Merged dev into feature/CE-1887-mobile-android-app

This commit is contained in:
2024-11-21 10:55:16 -06:00
12 changed files with 334 additions and 37 deletions

View File

@ -40,9 +40,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS) ** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS)
** - alertHtml - html to display inside the alert (other than its icon) ** - alertHtml - html to display inside the alert (other than its icon)
*******************************************************************************/ *******************************************************************************/
public class ProcessAlertWidget extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData> public class AlertWidgetRenderer extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
{ {
public static final String NAME = "ProcessAlertWidget"; public static final String NAME = "AlertWidgetRenderer";

View File

@ -301,6 +301,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
} }
} }
widgetData.setAllowRecordEdit(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordEdit"))));
widgetData.setAllowRecordDelete(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordDelete"))));
return (new RenderWidgetOutput(widgetData)); return (new RenderWidgetOutput(widgetData));
} }
catch(Exception e) catch(Exception e)

View File

@ -28,7 +28,6 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -468,7 +467,8 @@ public class QValueFormatter
{ {
for(QFieldMetaData field : table.getFields().values()) for(QFieldMetaData field : table.getFields().values())
{ {
if(field.getType().equals(QFieldType.BLOB)) Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD);
if(fileDownloadAdornment.isPresent())
{ {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// file name comes from: // // file name comes from: //
@ -478,20 +478,7 @@ public class QValueFormatter
// - tableLabel primaryKey fieldLabel // // - tableLabel primaryKey fieldLabel //
// - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) // // - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD); Map<String, Serializable> adornmentValues = fileDownloadAdornment.get().getValues();
Map<String, Serializable> adornmentValues = Collections.emptyMap();
if(fileDownloadAdornment.isPresent())
{
adornmentValues = fileDownloadAdornment.get().getValues();
}
else
{
///////////////////////////////////////////////////////
// don't change blobs unless they are file-downloads //
///////////////////////////////////////////////////////
continue;
}
String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD)); String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD));
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT)); String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
@ -542,7 +529,13 @@ public class QValueFormatter
} }
} }
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName); /////////////////////////////////////////////
// if field type is blob, update its value //
/////////////////////////////////////////////
if(QFieldType.BLOB.equals(field.getType()))
{
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName);
}
record.setDisplayValue(field.getName(), fileName); record.setDisplayValue(field.getName(), fileName);
} }
} }

View File

@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
public class StorageInput extends AbstractTableActionInput public class StorageInput extends AbstractTableActionInput
{ {
private String reference; private String reference;
private String contentType;
@ -74,4 +75,35 @@ public class StorageInput extends AbstractTableActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for contentType
*******************************************************************************/
public String getContentType()
{
return (this.contentType);
}
/*******************************************************************************
** Setter for contentType
*******************************************************************************/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
/*******************************************************************************
** Fluent setter for contentType
*******************************************************************************/
public StorageInput withContentType(String contentType)
{
this.contentType = contentType;
return (this);
}
} }

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.dashboard.widgets; package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
/******************************************************************************* /*******************************************************************************
** Model containing datastructure expected by frontend alert widget ** Model containing datastructure expected by frontend alert widget
** **
@ -40,8 +43,10 @@ public class AlertData extends QWidgetData
private String html; private String html;
private AlertType alertType; private AlertType alertType;
private Boolean hideWidget = false;
private List<String> bulletList;
@ -139,4 +144,66 @@ public class AlertData extends QWidgetData
return (this); return (this);
} }
/*******************************************************************************
** Getter for hideWidget
*******************************************************************************/
public boolean getHideWidget()
{
return (this.hideWidget);
}
/*******************************************************************************
** Setter for hideWidget
*******************************************************************************/
public void setHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
}
/*******************************************************************************
** Fluent setter for hideWidget
*******************************************************************************/
public AlertData withHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
return (this);
}
/*******************************************************************************
** Getter for bulletList
*******************************************************************************/
public List<String> getBulletList()
{
return (this.bulletList);
}
/*******************************************************************************
** Setter for bulletList
*******************************************************************************/
public void setBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
}
/*******************************************************************************
** Fluent setter for bulletList
*******************************************************************************/
public AlertData withBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
return (this);
}
} }

View File

@ -39,9 +39,13 @@ public class ChildRecordListData extends QWidgetData
private QueryOutput queryOutput; private QueryOutput queryOutput;
private QTableMetaData childTableMetaData; private QTableMetaData childTableMetaData;
private String tableName;
private String tablePath; private String tablePath;
private String viewAllLink; private String viewAllLink;
private Integer totalRows; private Integer totalRows;
private Boolean disableRowClick = false;
private Boolean allowRecordEdit = false;
private Boolean allowRecordDelete = false;
private boolean canAddChildRecord = false; private boolean canAddChildRecord = false;
private Map<String, Serializable> defaultValuesForNewChildRecords; private Map<String, Serializable> defaultValuesForNewChildRecords;
@ -352,4 +356,141 @@ public class ChildRecordListData extends QWidgetData
return (this); return (this);
} }
/*******************************************************************************
** Getter for tableName
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public ChildRecordListData withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Fluent setter for tablePath
*******************************************************************************/
public ChildRecordListData withTablePath(String tablePath)
{
this.tablePath = tablePath;
return (this);
}
/*******************************************************************************
** Getter for disableRowClick
*******************************************************************************/
public Boolean getDisableRowClick()
{
return (this.disableRowClick);
}
/*******************************************************************************
** Setter for disableRowClick
*******************************************************************************/
public void setDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
}
/*******************************************************************************
** Fluent setter for disableRowClick
*******************************************************************************/
public ChildRecordListData withDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
return (this);
}
/*******************************************************************************
** Getter for allowRecordEdit
*******************************************************************************/
public Boolean getAllowRecordEdit()
{
return (this.allowRecordEdit);
}
/*******************************************************************************
** Setter for allowRecordEdit
*******************************************************************************/
public void setAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
}
/*******************************************************************************
** Fluent setter for allowRecordEdit
*******************************************************************************/
public ChildRecordListData withAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
return (this);
}
/*******************************************************************************
** Getter for allowRecordDelete
*******************************************************************************/
public Boolean getAllowRecordDelete()
{
return (this.allowRecordDelete);
}
/*******************************************************************************
** Setter for allowRecordDelete
*******************************************************************************/
public void setAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
}
/*******************************************************************************
** Fluent setter for allowRecordDelete
*******************************************************************************/
public ChildRecordListData withAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
return (this);
}
} }

View File

@ -94,6 +94,29 @@ public class CountingHash<K extends Serializable> extends AbstractMap<K, Integer
/*******************************************************************************
** increment the value for the specified key
**
*******************************************************************************/
public Integer put(K key)
{
return (add(key));
}
/*******************************************************************************
** Set the value for the specified key by the supplied value
**
*******************************************************************************/
public Integer put(K key, Integer value)
{
this.map.put(key, value);
return (value);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -35,9 +35,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
/******************************************************************************* /*******************************************************************************
** Unit test for ProcessAlertWidget ** Unit test for AlertWidgetRenderer
*******************************************************************************/ *******************************************************************************/
class ProcessAlertWidgetTest extends BaseTest class AlertWidgetRendererTest extends BaseTest
{ {
/******************************************************************************* /*******************************************************************************
@ -46,10 +46,10 @@ class ProcessAlertWidgetTest extends BaseTest
@Test @Test
void test() throws QException void test() throws QException
{ {
MetaDataProducerHelper.processAllMetaDataProducersInPackage(QContext.getQInstance(), ProcessAlertWidget.class.getPackageName()); MetaDataProducerHelper.processAllMetaDataProducersInPackage(QContext.getQInstance(), AlertWidgetRenderer.class.getPackageName());
RenderWidgetInput input = new RenderWidgetInput(); RenderWidgetInput input = new RenderWidgetInput();
input.setWidgetMetaData(QContext.getQInstance().getWidget(ProcessAlertWidget.NAME)); input.setWidgetMetaData(QContext.getQInstance().getWidget(AlertWidgetRenderer.NAME));
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// make sure we run w/o exceptions (and w/ default outputs) if there are no query params // // make sure we run w/o exceptions (and w/ default outputs) if there are no query params //
@ -69,4 +69,4 @@ class ProcessAlertWidgetTest extends BaseTest
} }
} }

View File

@ -30,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
/******************************************************************************* /*******************************************************************************
** Unit test for CountingHash ** Unit test for CountingHash
*******************************************************************************/ *******************************************************************************/
class CountingHashTest extends BaseTest class CountingHashTest extends BaseTest
{ {
@ -73,4 +73,19 @@ class CountingHashTest extends BaseTest
assertEquals(1, alwaysMutable.get("B")); assertEquals(1, alwaysMutable.get("B"));
} }
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPut()
{
CountingHash<String> alwaysMutable = new CountingHash<>(Map.of("A", 5));
alwaysMutable.put("A", 25);
assertEquals(25, alwaysMutable.get("A"));
alwaysMutable.put("A");
assertEquals(26, alwaysMutable.get("A"));
}
}

View File

@ -58,7 +58,7 @@ public class S3StorageAction extends AbstractS3Action implements QStorageInterfa
AmazonS3 amazonS3 = getS3Utils().getAmazonS3(); AmazonS3 amazonS3 = getS3Utils().getAmazonS3();
String fullPath = getFullPath(storageInput); String fullPath = getFullPath(storageInput);
S3UploadOutputStream s3UploadOutputStream = new S3UploadOutputStream(amazonS3, backend.getBucketName(), fullPath); S3UploadOutputStream s3UploadOutputStream = new S3UploadOutputStream(amazonS3, backend.getBucketName(), fullPath, storageInput.getContentType());
return (s3UploadOutputStream); return (s3UploadOutputStream);
} }
catch(Exception e) catch(Exception e)

View File

@ -53,6 +53,7 @@ public class S3UploadOutputStream extends OutputStream
private final AmazonS3 amazonS3; private final AmazonS3 amazonS3;
private final String bucketName; private final String bucketName;
private final String key; private final String key;
private final String contentType;
private byte[] buffer = new byte[5 * 1024 * 1024]; private byte[] buffer = new byte[5 * 1024 * 1024];
private int offset = 0; private int offset = 0;
@ -68,11 +69,12 @@ public class S3UploadOutputStream extends OutputStream
** Constructor ** Constructor
** **
*******************************************************************************/ *******************************************************************************/
public S3UploadOutputStream(AmazonS3 amazonS3, String bucketName, String key) public S3UploadOutputStream(AmazonS3 amazonS3, String bucketName, String key, String contentType)
{ {
this.amazonS3 = amazonS3; this.amazonS3 = amazonS3;
this.bucketName = bucketName; this.bucketName = bucketName;
this.key = key; this.key = key;
this.contentType = contentType;
} }
@ -96,6 +98,13 @@ public class S3UploadOutputStream extends OutputStream
*******************************************************************************/ *******************************************************************************/
private void uploadIfNeeded() private void uploadIfNeeded()
{ {
ObjectMetadata objectMetadata = null;
if(this.contentType != null)
{
objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(this.contentType);
}
if(offset == buffer.length) if(offset == buffer.length)
{ {
////////////////////////////////////////// //////////////////////////////////////////
@ -104,7 +113,8 @@ public class S3UploadOutputStream extends OutputStream
if(initiateMultipartUploadResult == null) if(initiateMultipartUploadResult == null)
{ {
LOG.info("Initiating a multipart upload", logPair("key", key)); LOG.info("Initiating a multipart upload", logPair("key", key));
initiateMultipartUploadResult = amazonS3.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key)); initiateMultipartUploadResult = amazonS3.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key, objectMetadata));
uploadPartResultList = new ArrayList<>(); uploadPartResultList = new ArrayList<>();
} }
@ -115,7 +125,8 @@ public class S3UploadOutputStream extends OutputStream
.withInputStream(new ByteArrayInputStream(buffer)) .withInputStream(new ByteArrayInputStream(buffer))
.withBucketName(bucketName) .withBucketName(bucketName)
.withKey(key) .withKey(key)
.withPartSize(buffer.length); .withPartSize(buffer.length)
.withObjectMetadata(objectMetadata);
uploadPartResultList.add(amazonS3.uploadPart(uploadPartRequest)); uploadPartResultList.add(amazonS3.uploadPart(uploadPartRequest));
@ -166,6 +177,13 @@ public class S3UploadOutputStream extends OutputStream
return; return;
} }
ObjectMetadata objectMetadata = null;
if(this.contentType != null)
{
objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(this.contentType);
}
if(initiateMultipartUploadResult != null) if(initiateMultipartUploadResult != null)
{ {
if(offset > 0) if(offset > 0)
@ -180,7 +198,8 @@ public class S3UploadOutputStream extends OutputStream
.withInputStream(new ByteArrayInputStream(buffer, 0, offset)) .withInputStream(new ByteArrayInputStream(buffer, 0, offset))
.withBucketName(bucketName) .withBucketName(bucketName)
.withKey(key) .withKey(key)
.withPartSize(offset); .withPartSize(offset)
.withObjectMetadata(objectMetadata);
uploadPartResultList.add(amazonS3.uploadPart(uploadPartRequest)); uploadPartResultList.add(amazonS3.uploadPart(uploadPartRequest));
} }
@ -193,8 +212,12 @@ public class S3UploadOutputStream extends OutputStream
} }
else else
{ {
if(objectMetadata == null)
{
objectMetadata = new ObjectMetadata();
}
LOG.info("Putting object (non-multipart)", logPair("key", key), logPair("length", offset)); LOG.info("Putting object (non-multipart)", logPair("key", key), logPair("length", offset));
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(offset); objectMetadata.setContentLength(offset);
PutObjectResult putObjectResult = amazonS3.putObject(bucketName, key, new ByteArrayInputStream(buffer, 0, offset), objectMetadata); PutObjectResult putObjectResult = amazonS3.putObject(bucketName, key, new ByteArrayInputStream(buffer, 0, offset), objectMetadata);
} }

View File

@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test;
/******************************************************************************* /*******************************************************************************
** Unit test for S3UploadOutputStream ** Unit test for S3UploadOutputStream
*******************************************************************************/ *******************************************************************************/
class S3UploadOutputStreamTest extends BaseS3Test class S3UploadOutputStreamTest extends BaseS3Test
{ {
@ -57,11 +57,11 @@ class S3UploadOutputStreamTest extends BaseS3Test
outputStream.write("\n]\n".getBytes(StandardCharsets.UTF_8)); outputStream.write("\n]\n".getBytes(StandardCharsets.UTF_8));
outputStream.close(); outputStream.close();
S3UploadOutputStream s3UploadOutputStream = new S3UploadOutputStream(getS3Utils().getAmazonS3(), bucketName, key); S3UploadOutputStream s3UploadOutputStream = new S3UploadOutputStream(getS3Utils().getAmazonS3(), bucketName, key, null);
s3UploadOutputStream.write(outputStream.toByteArray(), 0, 5 * 1024 * 1024); s3UploadOutputStream.write(outputStream.toByteArray(), 0, 5 * 1024 * 1024);
s3UploadOutputStream.write(outputStream.toByteArray(), 0, 3 * 1024 * 1024); s3UploadOutputStream.write(outputStream.toByteArray(), 0, 3 * 1024 * 1024);
s3UploadOutputStream.write(outputStream.toByteArray(), 0, 3 * 1024 * 1024); s3UploadOutputStream.write(outputStream.toByteArray(), 0, 3 * 1024 * 1024);
s3UploadOutputStream.close(); s3UploadOutputStream.close();
} }
} }