mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-22 15:08:45 +00:00
Compare commits
19 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
d417c2c93a | |||
1d42b3eb16 | |||
62d4c17aba | |||
76028ddcaa | |||
8829408e54 | |||
0a35d02404 | |||
8c882b8476 | |||
3d729d67e6 | |||
984e37bcd8 | |||
3f16f4c0c3 | |||
29b8025c41 | |||
9f4cb02764 | |||
a32c4f4936 | |||
20a13161c5 | |||
c37056f942 | |||
9281d07e96 | |||
570d1a80b5 | |||
5e4305d1d5 | |||
a591f57591 |
2
pom.xml
2
pom.xml
@ -54,7 +54,7 @@
|
|||||||
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
|
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
|
||||||
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
||||||
<coverage.haltOnFailure>true</coverage.haltOnFailure>
|
<coverage.haltOnFailure>true</coverage.haltOnFailure>
|
||||||
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
|
<coverage.instructionCoveredRatioMinimum>0.75</coverage.instructionCoveredRatioMinimum>
|
||||||
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
|
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
|
||||||
<plugin.shade.phase>none</plugin.shade.phase>
|
<plugin.shade.phase>none</plugin.shade.phase>
|
||||||
</properties>
|
</properties>
|
||||||
|
@ -173,6 +173,19 @@
|
|||||||
<version>1.12.321</version>
|
<version>1.12.321</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.amazonaws</groupId>
|
||||||
|
<artifactId>aws-java-sdk-ses</artifactId>
|
||||||
|
<version>1.12.705</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cloud.localstack</groupId>
|
||||||
|
<artifactId>localstack-utils</artifactId>
|
||||||
|
<version>0.2.20</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
<artifactId>quartz</artifactId>
|
<artifactId>quartz</artifactId>
|
||||||
@ -186,6 +199,12 @@
|
|||||||
<version>2.23.0</version>
|
<version>2.23.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>jakarta.mail</artifactId>
|
||||||
|
<version>2.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Common deps for all qqq modules -->
|
<!-- Common deps for all qqq modules -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
@ -59,6 +60,7 @@ import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
|||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -66,6 +68,9 @@ import org.apache.commons.lang.BooleanUtils;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||||
{
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ChildRecordListRenderer.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
@ -171,6 +176,8 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
String widgetLabel = input.getQueryParams().get("widgetLabel");
|
String widgetLabel = input.getQueryParams().get("widgetLabel");
|
||||||
String joinName = input.getQueryParams().get("joinName");
|
String joinName = input.getQueryParams().get("joinName");
|
||||||
@ -293,5 +300,11 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
|||||||
|
|
||||||
return (new RenderWidgetOutput(widgetData));
|
return (new RenderWidgetOutput(widgetData));
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error rendering child record list", e, logPair("widgetName", () -> input.getWidgetMetaData().getName()));
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,4 +46,24 @@ public interface QStorageInterface
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
InputStream getInputStream(StorageInput storageInput) throws QException;
|
InputStream getInputStream(StorageInput storageInput) throws QException;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default void makePublic(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
//////////
|
||||||
|
// noop //
|
||||||
|
//////////
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default String getDownloadURL(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.MessagingProviderInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.QMessagingProviderDispatcher;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SendMessageAction extends AbstractQActionFunction<SendMessageInput, SendMessageOutput>
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public SendMessageOutput execute(SendMessageInput input) throws QException
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(input.getMessagingProviderName()))
|
||||||
|
{
|
||||||
|
throw (new QException("Messaging provider name was not given in SendMessageInput."));
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessagingProviderMetaData messagingProvider = QContext.getQInstance().getMessagingProvider(input.getMessagingProviderName());
|
||||||
|
if(messagingProvider == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Messaging provider named [" + input.getMessagingProviderName() + "] was not found in this QInstance."));
|
||||||
|
}
|
||||||
|
|
||||||
|
MessagingProviderInterface messagingProviderInterface = new QMessagingProviderDispatcher().getMessagingProviderInterface(messagingProvider.getType());
|
||||||
|
|
||||||
|
return (messagingProviderInterface.sendMessage(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.processes;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -114,4 +115,14 @@ public class QProcessCallbackFactory
|
|||||||
return (forFilter(new QQueryFilter().withCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, value))));
|
return (forFilter(new QQueryFilter().withCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, value))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QProcessCallback forPrimaryKeys(String fieldName, Collection<? extends Serializable> values)
|
||||||
|
{
|
||||||
|
return (forFilter(new QQueryFilter().withCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.IN, values))));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,7 @@ public class QueryAction
|
|||||||
nextLevelQueryInput.setTableName(association.getAssociatedTableName());
|
nextLevelQueryInput.setTableName(association.getAssociatedTableName());
|
||||||
nextLevelQueryInput.setIncludeAssociations(true);
|
nextLevelQueryInput.setIncludeAssociations(true);
|
||||||
nextLevelQueryInput.setAssociationNamesToInclude(buildNextLevelAssociationNamesToInclude(association.getName(), queryInput.getAssociationNamesToInclude()));
|
nextLevelQueryInput.setAssociationNamesToInclude(buildNextLevelAssociationNamesToInclude(association.getName(), queryInput.getAssociationNamesToInclude()));
|
||||||
|
nextLevelQueryInput.setTransaction(queryInput.getTransaction());
|
||||||
|
|
||||||
QQueryFilter filter = new QQueryFilter();
|
QQueryFilter filter = new QQueryFilter();
|
||||||
nextLevelQueryInput.setFilter(filter);
|
nextLevelQueryInput.setFilter(filter);
|
||||||
|
@ -93,4 +93,28 @@ public class StorageAction
|
|||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
||||||
return (qModule);
|
return (qModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void makePublic(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
QBackendModuleInterface qBackendModuleInterface = preAction(storageInput);
|
||||||
|
QStorageInterface storageInterface = qBackendModuleInterface.getStorageInterface();
|
||||||
|
storageInterface.makePublic(storageInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
QBackendModuleInterface qBackendModuleInterface = preAction(storageInput);
|
||||||
|
QStorageInterface storageInterface = qBackendModuleInterface.getStorageInterface();
|
||||||
|
return (storageInterface.getDownloadURL(storageInput));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,13 +403,10 @@ public class ValidateRecordSecurityLockHelper
|
|||||||
|
|
||||||
if(action.equals(Action.UPDATE))
|
if(action.equals(Action.UPDATE))
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
// todo at some point this seemed right, but now it doesn't - needs work. //
|
// when doing an update, convert all OR's to AND's... //
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
// ////////////////////////////////////////////////////////
|
updateOperators(locksOfType, MultiRecordSecurityLock.BooleanOperator.AND);
|
||||||
// // when doing an update, convert all OR's to AND's... //
|
|
||||||
// ////////////////////////////////////////////////////////
|
|
||||||
// updateOperators(locksOfType, MultiRecordSecurityLock.BooleanOperator.AND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class Attachment
|
||||||
|
{
|
||||||
|
private byte[] contents;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for contents
|
||||||
|
*******************************************************************************/
|
||||||
|
public byte[] getContents()
|
||||||
|
{
|
||||||
|
return (this.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for contents
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setContents(byte[] contents)
|
||||||
|
{
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for contents
|
||||||
|
*******************************************************************************/
|
||||||
|
public Attachment withContents(byte[] contents)
|
||||||
|
{
|
||||||
|
this.contents = contents;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return (this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public Attachment withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class Content
|
||||||
|
{
|
||||||
|
private String body;
|
||||||
|
private ContentRole contentRole;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for body
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getBody()
|
||||||
|
{
|
||||||
|
return (this.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for body
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setBody(String body)
|
||||||
|
{
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for body
|
||||||
|
*******************************************************************************/
|
||||||
|
public Content withBody(String body)
|
||||||
|
{
|
||||||
|
this.body = body;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for contentRole
|
||||||
|
*******************************************************************************/
|
||||||
|
public ContentRole getContentRole()
|
||||||
|
{
|
||||||
|
return (this.contentRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for contentRole
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setContentRole(ContentRole contentRole)
|
||||||
|
{
|
||||||
|
this.contentRole = contentRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for contentRole
|
||||||
|
*******************************************************************************/
|
||||||
|
public Content withContentRole(ContentRole contentRole)
|
||||||
|
{
|
||||||
|
this.contentRole = contentRole;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface ContentRole
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
enum Default implements ContentRole
|
||||||
|
{
|
||||||
|
DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class MultiParty extends Party
|
||||||
|
{
|
||||||
|
private List<Party> partyList;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for partyList
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Party> getPartyList()
|
||||||
|
{
|
||||||
|
return (this.partyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for partyList
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setPartyList(List<Party> partyList)
|
||||||
|
{
|
||||||
|
this.partyList = partyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for partyList
|
||||||
|
*******************************************************************************/
|
||||||
|
public MultiParty withPartyList(List<Party> partyList)
|
||||||
|
{
|
||||||
|
this.partyList = partyList;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public MultiParty withParty(Party party)
|
||||||
|
{
|
||||||
|
addParty(party);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void addParty(Party party)
|
||||||
|
{
|
||||||
|
if(this.partyList == null)
|
||||||
|
{
|
||||||
|
this.partyList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.partyList.add(party);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class Party
|
||||||
|
{
|
||||||
|
private String label;
|
||||||
|
private String address;
|
||||||
|
private PartyRole role;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for label
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return (this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for label
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for label
|
||||||
|
*******************************************************************************/
|
||||||
|
public Party withLabel(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for address
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getAddress()
|
||||||
|
{
|
||||||
|
return (this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for address
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAddress(String address)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for address
|
||||||
|
*******************************************************************************/
|
||||||
|
public Party withAddress(String address)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for role
|
||||||
|
*******************************************************************************/
|
||||||
|
public PartyRole getRole()
|
||||||
|
{
|
||||||
|
return (this.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for role
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRole(PartyRole role)
|
||||||
|
{
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for role
|
||||||
|
*******************************************************************************/
|
||||||
|
public Party withRole(PartyRole role)
|
||||||
|
{
|
||||||
|
this.role = role;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface PartyRole
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
enum Default implements PartyRole
|
||||||
|
{
|
||||||
|
DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,246 +19,259 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.backend.core.model.savedviews;
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.ArrayList;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Entity bean for the shared saved view table
|
|
||||||
*******************************************************************************/
|
|
||||||
public class SharedSavedView extends QRecordEntity
|
|
||||||
{
|
|
||||||
public static final String TABLE_NAME = "sharedSavedView";
|
|
||||||
|
|
||||||
@QField(isEditable = false)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@QField(isEditable = false)
|
|
||||||
private Instant createDate;
|
|
||||||
|
|
||||||
@QField(isEditable = false)
|
|
||||||
private Instant modifyDate;
|
|
||||||
|
|
||||||
@QField(possibleValueSourceName = SavedView.TABLE_NAME, label = "View")
|
|
||||||
private Integer savedViewId;
|
|
||||||
|
|
||||||
@QField(label = "User")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@QField(possibleValueSourceName = ShareScopePossibleValueMetaDataProducer.NAME)
|
|
||||||
private String scope;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Constructor
|
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public SharedSavedView()
|
public class SendMessageInput extends AbstractActionInput
|
||||||
{
|
{
|
||||||
|
private String messagingProviderName;
|
||||||
|
private Party to;
|
||||||
|
private Party from;
|
||||||
|
private String subject;
|
||||||
|
private List<Content> contentList;
|
||||||
|
private List<Attachment> attachmentList;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for to
|
||||||
|
*******************************************************************************/
|
||||||
|
public Party getTo()
|
||||||
|
{
|
||||||
|
return (this.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for to
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTo(Party to)
|
||||||
|
{
|
||||||
|
this.to = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for to
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageInput withTo(Party to)
|
||||||
|
{
|
||||||
|
this.to = to;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for from
|
||||||
|
*******************************************************************************/
|
||||||
|
public Party getFrom()
|
||||||
|
{
|
||||||
|
return (this.from);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for from
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFrom(Party from)
|
||||||
|
{
|
||||||
|
this.from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for from
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageInput withFrom(Party from)
|
||||||
|
{
|
||||||
|
this.from = from;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSubject()
|
||||||
|
{
|
||||||
|
return (this.subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSubject(String subject)
|
||||||
|
{
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageInput withSubject(String subject)
|
||||||
|
{
|
||||||
|
this.subject = subject;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for contentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Content> getContentList()
|
||||||
|
{
|
||||||
|
return (this.contentList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for contentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setContentList(List<Content> contentList)
|
||||||
|
{
|
||||||
|
this.contentList = contentList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for contentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageInput withContentList(List<Content> contentList)
|
||||||
|
{
|
||||||
|
this.contentList = contentList;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for attachmentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Attachment> getAttachmentList()
|
||||||
|
{
|
||||||
|
return (this.attachmentList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for attachmentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAttachmentList(List<Attachment> attachmentList)
|
||||||
|
{
|
||||||
|
this.attachmentList = attachmentList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for attachmentList
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageInput withAttachmentList(List<Attachment> attachmentList)
|
||||||
|
{
|
||||||
|
this.attachmentList = attachmentList;
|
||||||
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor
|
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public SharedSavedView(QRecord qRecord) throws QException
|
public SendMessageInput withContent(Content content)
|
||||||
{
|
{
|
||||||
populateFromQRecord(qRecord);
|
addContent(content);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for id
|
|
||||||
*******************************************************************************/
|
|
||||||
public Integer getId()
|
|
||||||
{
|
|
||||||
return (this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for id
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setId(Integer id)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for id
|
|
||||||
*******************************************************************************/
|
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withId(Integer id)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for createDate
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Instant getCreateDate()
|
public void addContent(Content content)
|
||||||
{
|
{
|
||||||
return (this.createDate);
|
if(this.contentList == null)
|
||||||
|
{
|
||||||
|
this.contentList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.contentList.add(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for createDate
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setCreateDate(Instant createDate)
|
public SendMessageInput withAttachment(Attachment attachment)
|
||||||
{
|
{
|
||||||
this.createDate = createDate;
|
addAttachment(attachment);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for createDate
|
|
||||||
*******************************************************************************/
|
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withCreateDate(Instant createDate)
|
|
||||||
{
|
|
||||||
this.createDate = createDate;
|
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for modifyDate
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Instant getModifyDate()
|
public void addAttachment(Attachment attachment)
|
||||||
{
|
{
|
||||||
return (this.modifyDate);
|
if(this.attachmentList == null)
|
||||||
|
{
|
||||||
|
this.attachmentList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.attachmentList.add(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for modifyDate
|
** Getter for messagingProviderName
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setModifyDate(Instant modifyDate)
|
public String getMessagingProviderName()
|
||||||
{
|
{
|
||||||
this.modifyDate = modifyDate;
|
return (this.messagingProviderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for modifyDate
|
** Setter for messagingProviderName
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withModifyDate(Instant modifyDate)
|
public void setMessagingProviderName(String messagingProviderName)
|
||||||
{
|
{
|
||||||
this.modifyDate = modifyDate;
|
this.messagingProviderName = messagingProviderName;
|
||||||
return (this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for savedViewId
|
** Fluent setter for messagingProviderName
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Integer getSavedViewId()
|
public SendMessageInput withMessagingProviderName(String messagingProviderName)
|
||||||
{
|
{
|
||||||
return (this.savedViewId);
|
this.messagingProviderName = messagingProviderName;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for savedViewId
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setSavedViewId(Integer savedViewId)
|
|
||||||
{
|
|
||||||
this.savedViewId = savedViewId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for savedViewId
|
|
||||||
*******************************************************************************/
|
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withSavedViewId(Integer savedViewId)
|
|
||||||
{
|
|
||||||
this.savedViewId = savedViewId;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for userId
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getUserId()
|
|
||||||
{
|
|
||||||
return (this.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for userId
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setUserId(String userId)
|
|
||||||
{
|
|
||||||
this.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for userId
|
|
||||||
*******************************************************************************/
|
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withUserId(String userId)
|
|
||||||
{
|
|
||||||
this.userId = userId;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for scope
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getScope()
|
|
||||||
{
|
|
||||||
return (this.scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for scope
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setScope(String scope)
|
|
||||||
{
|
|
||||||
this.scope = scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for scope
|
|
||||||
*******************************************************************************/
|
|
||||||
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withScope(String scope)
|
|
||||||
{
|
|
||||||
this.scope = scope;
|
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SendMessageOutput extends AbstractActionOutput
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.ContentRole;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum EmailContentRole implements ContentRole
|
||||||
|
{
|
||||||
|
TEXT,
|
||||||
|
HTML
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum EmailPartyRole implements PartyRole
|
||||||
|
{
|
||||||
|
TO,
|
||||||
|
CC,
|
||||||
|
BCC,
|
||||||
|
FROM,
|
||||||
|
REPLY_TO
|
||||||
|
}
|
@ -417,20 +417,4 @@ public class QueryJoin
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "QueryJoin{base="
|
|
||||||
+ baseTableOrAlias + ", joinTable='"
|
|
||||||
+ joinTable + ", joinMetaData="
|
|
||||||
+ (joinMetaData == null ? null : joinMetaData.getName()) + ", alias='"
|
|
||||||
+ alias + ", select="
|
|
||||||
+ select + ", type="
|
|
||||||
+ type + '}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class DynamicFormWidgetData extends QWidgetData
|
||||||
|
{
|
||||||
|
private List<QFieldMetaData> fieldList;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// if there are no fields, what message to display //
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
private String noFieldsMessage;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// what 1 field do we want to combine the dynamic fields into (as a JSON string) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private String mergedDynamicFormValuesIntoFieldName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String getType()
|
||||||
|
{
|
||||||
|
return WidgetType.DYNAMIC_FORM.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldList
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QFieldMetaData> getFieldList()
|
||||||
|
{
|
||||||
|
return (this.fieldList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for fieldList
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFieldList(List<QFieldMetaData> fieldList)
|
||||||
|
{
|
||||||
|
this.fieldList = fieldList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for fieldList
|
||||||
|
*******************************************************************************/
|
||||||
|
public DynamicFormWidgetData withFieldList(List<QFieldMetaData> fieldList)
|
||||||
|
{
|
||||||
|
this.fieldList = fieldList;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for noFieldsMessage
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getNoFieldsMessage()
|
||||||
|
{
|
||||||
|
return (this.noFieldsMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for noFieldsMessage
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setNoFieldsMessage(String noFieldsMessage)
|
||||||
|
{
|
||||||
|
this.noFieldsMessage = noFieldsMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for noFieldsMessage
|
||||||
|
*******************************************************************************/
|
||||||
|
public DynamicFormWidgetData withNoFieldsMessage(String noFieldsMessage)
|
||||||
|
{
|
||||||
|
this.noFieldsMessage = noFieldsMessage;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for mergedDynamicFormValuesIntoFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getMergedDynamicFormValuesIntoFieldName()
|
||||||
|
{
|
||||||
|
return (this.mergedDynamicFormValuesIntoFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for mergedDynamicFormValuesIntoFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMergedDynamicFormValuesIntoFieldName(String mergedDynamicFormValuesIntoFieldName)
|
||||||
|
{
|
||||||
|
this.mergedDynamicFormValuesIntoFieldName = mergedDynamicFormValuesIntoFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for mergedDynamicFormValuesIntoFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public DynamicFormWidgetData withMergedDynamicFormValuesIntoFieldName(String mergedDynamicFormValuesIntoFieldName)
|
||||||
|
{
|
||||||
|
this.mergedDynamicFormValuesIntoFieldName = mergedDynamicFormValuesIntoFieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,10 +27,12 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public enum WidgetType
|
public enum WidgetType
|
||||||
{
|
{
|
||||||
|
///////////////////////////////////
|
||||||
|
// (generally) dashboard widgets //
|
||||||
|
///////////////////////////////////
|
||||||
ALERT("alert"),
|
ALERT("alert"),
|
||||||
BAR_CHART("barChart"),
|
BAR_CHART("barChart"),
|
||||||
CHART("chart"),
|
CHART("chart"),
|
||||||
CHILD_RECORD_LIST("childRecordList"),
|
|
||||||
DIVIDER("divider"),
|
DIVIDER("divider"),
|
||||||
FIELD_VALUE_LIST("fieldValueList"),
|
FIELD_VALUE_LIST("fieldValueList"),
|
||||||
GENERIC("generic"),
|
GENERIC("generic"),
|
||||||
@ -40,20 +42,34 @@ public enum WidgetType
|
|||||||
SMALL_LINE_CHART("smallLineChart"),
|
SMALL_LINE_CHART("smallLineChart"),
|
||||||
LOCATION("location"),
|
LOCATION("location"),
|
||||||
MULTI_STATISTICS("multiStatistics"),
|
MULTI_STATISTICS("multiStatistics"),
|
||||||
PARENT_WIDGET("parentWidget"),
|
|
||||||
PIE_CHART("pieChart"),
|
PIE_CHART("pieChart"),
|
||||||
PROCESS("process"),
|
|
||||||
QUICK_SIGHT_CHART("quickSightChart"),
|
QUICK_SIGHT_CHART("quickSightChart"),
|
||||||
STATISTICS("statistics"),
|
STATISTICS("statistics"),
|
||||||
STACKED_BAR_CHART("stackedBarChart"),
|
STACKED_BAR_CHART("stackedBarChart"),
|
||||||
STEPPER("stepper"),
|
STEPPER("stepper"),
|
||||||
TABLE("table"),
|
TABLE("table"),
|
||||||
USA_MAP("usaMap"),
|
USA_MAP("usaMap"),
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// widget to house a process //
|
||||||
|
///////////////////////////////
|
||||||
|
PROCESS("process"),
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// container widgets //
|
||||||
|
///////////////////////
|
||||||
|
PARENT_WIDGET("parentWidget"),
|
||||||
COMPOSITE("composite"),
|
COMPOSITE("composite"),
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// record view/edit widgets //
|
||||||
|
//////////////////////////////
|
||||||
|
CHILD_RECORD_LIST("childRecordList"),
|
||||||
|
DYNAMIC_FORM("dynamicForm"),
|
||||||
DATA_BAG_VIEWER("dataBagViewer"),
|
DATA_BAG_VIEWER("dataBagViewer"),
|
||||||
SCRIPT_VIEWER("scriptViewer"),
|
PIVOT_TABLE_SETUP("pivotTableSetup"),
|
||||||
REPORT_SETUP("reportSetup"),
|
REPORT_SETUP("reportSetup"),
|
||||||
PIVOT_TABLE_SETUP("pivotTableSetup");
|
SCRIPT_VIEWER("scriptViewer");
|
||||||
|
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
|
@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
|
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
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.processes.QProcessMetaData;
|
||||||
@ -78,6 +79,7 @@ public class QInstance
|
|||||||
private QAuthenticationMetaData authentication = null;
|
private QAuthenticationMetaData authentication = null;
|
||||||
private QBrandingMetaData branding = null;
|
private QBrandingMetaData branding = null;
|
||||||
private Map<String, QAutomationProviderMetaData> automationProviders = new HashMap<>();
|
private Map<String, QAutomationProviderMetaData> automationProviders = new HashMap<>();
|
||||||
|
private Map<String, QMessagingProviderMetaData> messagingProviders = new HashMap<>();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||||
@ -739,6 +741,53 @@ public class QInstance
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void addMessagingProvider(QMessagingProviderMetaData messagingProvider)
|
||||||
|
{
|
||||||
|
String name = messagingProvider.getName();
|
||||||
|
if(this.messagingProviders.containsKey(name))
|
||||||
|
{
|
||||||
|
throw (new IllegalArgumentException("Attempted to add a second messagingProvider with name: " + name));
|
||||||
|
}
|
||||||
|
this.messagingProviders.put(name, messagingProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QMessagingProviderMetaData getMessagingProvider(String name)
|
||||||
|
{
|
||||||
|
return (this.messagingProviders.get(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for messagingProviders
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, QMessagingProviderMetaData> getMessagingProviders()
|
||||||
|
{
|
||||||
|
return messagingProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for messagingProviders
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMessagingProviders(Map<String, QMessagingProviderMetaData> messagingProviders)
|
||||||
|
{
|
||||||
|
this.messagingProviders = messagingProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for hasBeenValidated
|
** Getter for hasBeenValidated
|
||||||
**
|
**
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Base class for qqq messaging-providers. e.g., a connection to an outbound
|
||||||
|
** email service, or, for example, Slack.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QMessagingProviderMetaData implements TopLevelMetaDataInterface
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return (this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for name
|
||||||
|
*******************************************************************************/
|
||||||
|
public QMessagingProviderMetaData withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for type
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getType()
|
||||||
|
{
|
||||||
|
return (this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for type
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setType(String type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for type
|
||||||
|
*******************************************************************************/
|
||||||
|
public QMessagingProviderMetaData withType(String type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void addSelfToInstance(QInstance qInstance)
|
||||||
|
{
|
||||||
|
qInstance.addMessagingProvider(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.MessagingProviderInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class EmailMessagingProvider implements MessagingProviderInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String getType()
|
||||||
|
{
|
||||||
|
return (EmailMessagingProviderMetaData.TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
|
||||||
|
{
|
||||||
|
return new SendEmailAction().sendMessage(sendMessageInput);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.QMessagingProviderDispatcher;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class EmailMessagingProviderMetaData extends QMessagingProviderMetaData
|
||||||
|
{
|
||||||
|
private String smtpServer;
|
||||||
|
private String smtpPort;
|
||||||
|
|
||||||
|
public static final String TYPE = "EMAIL";
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
QMessagingProviderDispatcher.registerMessagingProvider(new EmailMessagingProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public EmailMessagingProviderMetaData()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
setType(TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for smtpServer
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSmtpServer()
|
||||||
|
{
|
||||||
|
return (this.smtpServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for smtpServer
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSmtpServer(String smtpServer)
|
||||||
|
{
|
||||||
|
this.smtpServer = smtpServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for smtpServer
|
||||||
|
*******************************************************************************/
|
||||||
|
public EmailMessagingProviderMetaData withSmtpServer(String smtpServer)
|
||||||
|
{
|
||||||
|
this.smtpServer = smtpServer;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for smtpPort
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSmtpPort()
|
||||||
|
{
|
||||||
|
return (this.smtpPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for smtpPort
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSmtpPort(String smtpPort)
|
||||||
|
{
|
||||||
|
this.smtpPort = smtpPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for smtpPort
|
||||||
|
*******************************************************************************/
|
||||||
|
public EmailMessagingProviderMetaData withSmtpPort(String smtpPort)
|
||||||
|
{
|
||||||
|
this.smtpPort = smtpPort;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import jakarta.mail.Address;
|
||||||
|
import jakarta.mail.Message;
|
||||||
|
import jakarta.mail.Multipart;
|
||||||
|
import jakarta.mail.Session;
|
||||||
|
import jakarta.mail.Transport;
|
||||||
|
import jakarta.mail.internet.AddressException;
|
||||||
|
import jakarta.mail.internet.InternetAddress;
|
||||||
|
import jakarta.mail.internet.MimeBodyPart;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
import jakarta.mail.internet.MimeMultipart;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SendEmailAction
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
|
||||||
|
{
|
||||||
|
EmailMessagingProviderMetaData messagingProvider = (EmailMessagingProviderMetaData) QContext.getQInstance().getMessagingProvider(sendMessageInput.getMessagingProviderName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// set up properties to make a session //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("mail.smtp.host", messagingProvider.getSmtpServer());
|
||||||
|
properties.setProperty("mail.smtp.port", messagingProvider.getSmtpPort());
|
||||||
|
Session session = Session.getInstance(properties);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// Construct a default MimeMessage object //
|
||||||
|
////////////////////////////////////////////
|
||||||
|
MimeMessage emailMessage = new MimeMessage(session);
|
||||||
|
emailMessage.setSubject(sendMessageInput.getSubject());
|
||||||
|
|
||||||
|
Party to = sendMessageInput.getTo();
|
||||||
|
if(to instanceof MultiParty toMultiParty)
|
||||||
|
{
|
||||||
|
for(Party party : toMultiParty.getPartyList())
|
||||||
|
{
|
||||||
|
addRecipient(emailMessage, party);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addRecipient(emailMessage, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
Party from = sendMessageInput.getFrom();
|
||||||
|
if(from instanceof MultiParty fromMultiParty)
|
||||||
|
{
|
||||||
|
for(Party party : fromMultiParty.getPartyList())
|
||||||
|
{
|
||||||
|
addSender(emailMessage, party);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addSender(emailMessage, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
Multipart multipart = new MimeMultipart();
|
||||||
|
for(Content content : sendMessageInput.getContentList())
|
||||||
|
{
|
||||||
|
if(EmailContentRole.HTML.equals(content.getContentRole()))
|
||||||
|
{
|
||||||
|
MimeBodyPart mimeBodyPart = new MimeBodyPart();
|
||||||
|
mimeBodyPart.setContent(content.getBody(), "text/html; charset=utf-8");
|
||||||
|
multipart.addBodyPart(mimeBodyPart);
|
||||||
|
}
|
||||||
|
else if(EmailContentRole.TEXT.equals(content.getContentRole()))
|
||||||
|
{
|
||||||
|
MimeBodyPart mimeBodyPart = new MimeBodyPart();
|
||||||
|
mimeBodyPart.setContent(content.getBody(), "text/plain; charset=utf-8");
|
||||||
|
multipart.addBodyPart(mimeBodyPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emailMessage.setContent(multipart);
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// send it //
|
||||||
|
/////////////
|
||||||
|
Transport.send(emailMessage);
|
||||||
|
System.out.println("Message dispatched successfully...");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error sending email", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void addSender(MimeMessage emailMessage, Party party) throws Exception
|
||||||
|
{
|
||||||
|
if(EmailPartyRole.REPLY_TO.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
InternetAddress internetAddress = getInternetAddressFromParty(party);
|
||||||
|
Address[] replyTo = emailMessage.getReplyTo();
|
||||||
|
if(replyTo == null || replyTo.length == 0)
|
||||||
|
{
|
||||||
|
emailMessage.setReplyTo(new Address[] { internetAddress });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<Address> replyToList = Arrays.asList(replyTo);
|
||||||
|
emailMessage.setReplyTo(replyToList.toArray(new Address[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.FROM.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
emailMessage.setFrom(getInternetAddressFromParty(party));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QException("Unrecognized sender role [" + party.getRole() + "]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void addRecipient(MimeMessage emailMessage, Party party) throws Exception
|
||||||
|
{
|
||||||
|
Message.RecipientType recipientType;
|
||||||
|
if(EmailPartyRole.CC.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
recipientType = Message.RecipientType.CC;
|
||||||
|
}
|
||||||
|
else if(EmailPartyRole.BCC.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
recipientType = Message.RecipientType.BCC;
|
||||||
|
}
|
||||||
|
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
recipientType = Message.RecipientType.TO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QException("Unrecognized recipient role [" + party.getRole() + "]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetAddress internetAddress = getInternetAddressFromParty(party);
|
||||||
|
emailMessage.addRecipient(recipientType, internetAddress);
|
||||||
|
System.out.println("add recipient: [" + recipientType + "] => [" + internetAddress + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static InternetAddress getInternetAddressFromParty(Party party) throws AddressException, UnsupportedEncodingException
|
||||||
|
{
|
||||||
|
InternetAddress internetAddress = new InternetAddress(party.getAddress());
|
||||||
|
if(StringUtils.hasContent(party.getLabel()))
|
||||||
|
{
|
||||||
|
internetAddress.setPersonal(party.getLabel());
|
||||||
|
}
|
||||||
|
return internetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.ses;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.MessagingProviderInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SESMessagingProvider implements MessagingProviderInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String getType()
|
||||||
|
{
|
||||||
|
return (SESMessagingProviderMetaData.TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
|
||||||
|
{
|
||||||
|
return new SendSESAction().sendMessage(sendMessageInput);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.ses;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.messaging.QMessagingProviderDispatcher;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SESMessagingProviderMetaData extends QMessagingProviderMetaData
|
||||||
|
{
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
public static final String TYPE = "SES";
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
QMessagingProviderDispatcher.registerMessagingProvider(new SESMessagingProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public SESMessagingProviderMetaData()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
setType(TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for accessKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getAccessKey()
|
||||||
|
{
|
||||||
|
return (this.accessKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for accessKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAccessKey(String accessKey)
|
||||||
|
{
|
||||||
|
this.accessKey = accessKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for accessKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public SESMessagingProviderMetaData withAccessKey(String accessKey)
|
||||||
|
{
|
||||||
|
this.accessKey = accessKey;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for secretKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSecretKey()
|
||||||
|
{
|
||||||
|
return (this.secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for secretKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSecretKey(String secretKey)
|
||||||
|
{
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for secretKey
|
||||||
|
*******************************************************************************/
|
||||||
|
public SESMessagingProviderMetaData withSecretKey(String secretKey)
|
||||||
|
{
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for region
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRegion()
|
||||||
|
{
|
||||||
|
return (this.region);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for region
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRegion(String region)
|
||||||
|
{
|
||||||
|
this.region = region;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for region
|
||||||
|
*******************************************************************************/
|
||||||
|
public SESMessagingProviderMetaData withRegion(String region)
|
||||||
|
{
|
||||||
|
this.region = region;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.ses;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||||
|
import com.amazonaws.auth.BasicAWSCredentials;
|
||||||
|
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
|
||||||
|
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Body;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Content;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Destination;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Message;
|
||||||
|
import com.amazonaws.services.simpleemail.model.SendEmailRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SendSESAction
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(SendSESAction.class);
|
||||||
|
|
||||||
|
private AmazonSimpleEmailService amazonSES;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AmazonSimpleEmailService client = getAmazonSES(sendMessageInput);
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// build up a send email request //
|
||||||
|
///////////////////////////////////
|
||||||
|
SendEmailRequest request = new SendEmailRequest()
|
||||||
|
.withSource(getSource(sendMessageInput))
|
||||||
|
.withReplyToAddresses(getReplyTos(sendMessageInput))
|
||||||
|
.withDestination(buildDestination(sendMessageInput))
|
||||||
|
.withMessage(buildMessage(sendMessageInput));
|
||||||
|
|
||||||
|
client.sendEmail(request);
|
||||||
|
LOG.info("SES Message [" + request.getMessage().getSubject().getData() + "] was sent to [" + request.getDestination().toString() + "].");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
String message = "An unexpected error occurred sending an SES message.";
|
||||||
|
throw (new QException(message, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
Message buildMessage(SendMessageInput input) throws QException
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// iterate over all contents of our input, looking for an HTML and Text version of the email //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Body body = new Body();
|
||||||
|
for(com.kingsrook.qqq.backend.core.model.actions.messaging.Content content : CollectionUtils.nonNullList(input.getContentList()))
|
||||||
|
{
|
||||||
|
if(EmailContentRole.TEXT.equals(content.getContentRole()))
|
||||||
|
{
|
||||||
|
body.setText(new Content().withCharset("UTF-8").withData(content.getBody()));
|
||||||
|
}
|
||||||
|
else if(EmailContentRole.HTML.equals(content.getContentRole()))
|
||||||
|
{
|
||||||
|
body.setHtml(new Content().withCharset("UTF-8").withData(content.getBody()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// error if no text or html body was provided //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
if(body.getText() == null && body.getHtml() == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Cannot send SES message because neither a 'Text' nor an 'HTML' body was provided."));
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// warning if no subject was provided //
|
||||||
|
////////////////////////////////////////
|
||||||
|
Message message = new Message();
|
||||||
|
message.setBody(body);
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// warn if no subject was provided //
|
||||||
|
/////////////////////////////////////
|
||||||
|
if(input.getSubject() == null)
|
||||||
|
{
|
||||||
|
LOG.warn("Sending SES message with no subject.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.setSubject(new Content().withCharset("UTF-8").withData(input.getSubject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
List<String> getReplyTos(SendMessageInput input) throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////
|
||||||
|
// no input, no reply tos //
|
||||||
|
////////////////////////////
|
||||||
|
if(input == null)
|
||||||
|
{
|
||||||
|
return (Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// build up a list of froms if multi //
|
||||||
|
///////////////////////////////////////
|
||||||
|
List<Party> partyList = getPartyListFromParty(input.getFrom());
|
||||||
|
if(partyList == null)
|
||||||
|
{
|
||||||
|
return (Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// only get reply to parties //
|
||||||
|
///////////////////////////////
|
||||||
|
List<Party> replyToParties = partyList.stream().filter(p -> EmailPartyRole.REPLY_TO.equals(p.getRole())).toList();
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// get addresses from reply tos //
|
||||||
|
//////////////////////////////////
|
||||||
|
List<String> replyTos = replyToParties.stream().map(Party::getAddress).toList();
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// return the from address //
|
||||||
|
/////////////////////////////
|
||||||
|
return (replyTos);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
String getSource(SendMessageInput input) throws QException
|
||||||
|
{
|
||||||
|
///////////////////////////////
|
||||||
|
// error if no from provided //
|
||||||
|
///////////////////////////////
|
||||||
|
if(input.getFrom() == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Cannot send SES message because a FROM was not provided."));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// build up a list of froms if multi //
|
||||||
|
///////////////////////////////////////
|
||||||
|
List<Party> partyList = getPartyListFromParty(input.getFrom());
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// remove any roles that aren't FROM //
|
||||||
|
///////////////////////////////////////
|
||||||
|
partyList.removeIf(p -> p.getRole() != null && !EmailPartyRole.FROM.equals(p.getRole()));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if no froms found, error, if more than one found, log a warning and use the first one //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(partyList.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QException("Cannot send SES message because a FROM was not provided."));
|
||||||
|
}
|
||||||
|
else if(partyList.size() > 1)
|
||||||
|
{
|
||||||
|
LOG.warn("More than one FROM value was found, will send using the first one found [" + partyList.get(0).getAddress() + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// return the from address //
|
||||||
|
/////////////////////////////
|
||||||
|
return (partyList.get(0).getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
List<Party> getPartyListFromParty(Party party)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// get all parties into one list of parties //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
List<Party> partyList = new ArrayList<>();
|
||||||
|
if(party != null)
|
||||||
|
{
|
||||||
|
if(party instanceof MultiParty toMultiParty)
|
||||||
|
{
|
||||||
|
partyList.addAll(toMultiParty.getPartyList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
partyList.add(party);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (partyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
Destination buildDestination(SendMessageInput input) throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// iterate over the parties putting it the proper party type list //
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
List<String> toList = new ArrayList<>();
|
||||||
|
List<String> ccList = new ArrayList<>();
|
||||||
|
List<String> bccList = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Party> partyList = getPartyListFromParty(input.getTo());
|
||||||
|
for(Party party : partyList)
|
||||||
|
{
|
||||||
|
if(EmailPartyRole.CC.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
ccList.add(party.getAddress());
|
||||||
|
}
|
||||||
|
else if(EmailPartyRole.BCC.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
bccList.add(party.getAddress());
|
||||||
|
}
|
||||||
|
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
|
||||||
|
{
|
||||||
|
toList.add(party.getAddress());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QException("An unrecognized recipient role of [" + party.getRole() + "] was provided."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// if no to addresses, this is an error //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
if(toList.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QException("Cannot send SES message because no TO addresses were provided."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// build and return aws destination object //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
return (new Destination()
|
||||||
|
.withToAddresses(toList)
|
||||||
|
.withCcAddresses(ccList)
|
||||||
|
.withBccAddresses(bccList));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Set the amazonSES object.
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAmazonSES(AmazonSimpleEmailService amazonSES)
|
||||||
|
{
|
||||||
|
this.amazonSES = amazonSES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Internal accessor for the amazonSES object - should always use this, not the field.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected AmazonSimpleEmailService getAmazonSES(SendMessageInput sendMessageInput)
|
||||||
|
{
|
||||||
|
if(amazonSES == null)
|
||||||
|
{
|
||||||
|
SESMessagingProviderMetaData messagingProvider = (SESMessagingProviderMetaData) QContext.getQInstance().getMessagingProvider(sendMessageInput.getMessagingProviderName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// get credentials and build an SES client //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
BasicAWSCredentials credentials = new BasicAWSCredentials(messagingProvider.getAccessKey(), messagingProvider.getSecretKey());
|
||||||
|
amazonSES = AmazonSimpleEmailServiceClientBuilder.standard()
|
||||||
|
.withCredentials(new AWSStaticCredentialsProvider(credentials))
|
||||||
|
.withRegion(messagingProvider.getRegion()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return amazonSES;
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@ public enum QComponentType
|
|||||||
RECORD_LIST,
|
RECORD_LIST,
|
||||||
PROCESS_SUMMARY_RESULTS,
|
PROCESS_SUMMARY_RESULTS,
|
||||||
GOOGLE_DRIVE_SELECT_FOLDER,
|
GOOGLE_DRIVE_SELECT_FOLDER,
|
||||||
|
WIDGET,
|
||||||
HTML;
|
HTML;
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// keep these values in sync with QComponentType.ts in qqq-frontend-core //
|
// keep these values in sync with QComponentType.ts in qqq-frontend-core //
|
||||||
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
|
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.widgets.RenderWidgetInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.DynamicFormWidgetData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Note - exists under 2 names, for the RenderSavedReport process, and for the
|
||||||
|
** ScheduledReport table
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ReportValuesDynamicFormWidgetRenderer extends AbstractWidgetRenderer
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ReportValuesDynamicFormWidgetRenderer.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<QFieldMetaData> fieldList = new ArrayList<>();
|
||||||
|
Map<String, String> defaultValues = new HashMap<>();
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// read params to ultimately find the query filter that has variables in it //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
SavedReport savedReport = null;
|
||||||
|
if(input.getQueryParams().containsKey("savedReportId"))
|
||||||
|
{
|
||||||
|
QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get("savedReportId"))));
|
||||||
|
savedReport = new SavedReport(record);
|
||||||
|
}
|
||||||
|
else if(input.getQueryParams().containsKey("id"))
|
||||||
|
{
|
||||||
|
QRecord scheduledReportRecord = new GetAction().executeForRecord(new GetInput(ScheduledReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(input.getQueryParams().get("id"))));
|
||||||
|
QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(ValueUtils.getValueAsInteger(scheduledReportRecord.getValueInteger("savedReportId"))));
|
||||||
|
savedReport = new SavedReport(record);
|
||||||
|
|
||||||
|
String inputValues = scheduledReportRecord.getValueString("inputValues");
|
||||||
|
if(StringUtils.hasContent(inputValues))
|
||||||
|
{
|
||||||
|
JSONObject jsonObject = JsonUtils.toJSONObject(inputValues);
|
||||||
|
for(String key : jsonObject.keySet())
|
||||||
|
{
|
||||||
|
defaultValues.put(key, jsonObject.optString(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//////////////////////////////////
|
||||||
|
// return quietly w/ nothing... //
|
||||||
|
//////////////////////////////////
|
||||||
|
DynamicFormWidgetData widgetData = new DynamicFormWidgetData();
|
||||||
|
return new RenderWidgetOutput(widgetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(savedReport.getQueryFilterJson()))
|
||||||
|
{
|
||||||
|
QQueryFilter queryFilter = SavedReportToReportMetaDataAdapter.getQQueryFilter(savedReport.getQueryFilterJson());
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(savedReport.getTableName());
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// find variables in the query filter; convert them to a list of fields for the dynamic form //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QFilterCriteria criteria : CollectionUtils.nonNullList(queryFilter.getCriteria()))
|
||||||
|
{
|
||||||
|
/////////////////////////////////
|
||||||
|
// todo - only variable fields //
|
||||||
|
/////////////////////////////////
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// todo - twice for "between" //
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// todo - join fields!! //
|
||||||
|
//////////////////////////
|
||||||
|
QFieldMetaData fieldMetaData = table.getField(criteria.getFieldName()).clone();
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// make name & label for field //
|
||||||
|
/////////////////////////////////
|
||||||
|
String operatorHumanish = StringUtils.allCapsToMixedCase(criteria.getOperator().name()); // todo match frontend..?
|
||||||
|
String fieldName = criteria.getFieldName() + operatorHumanish.replaceAll("_", "");
|
||||||
|
String label = fieldMetaData.getLabel() + " " + operatorHumanish.replaceAll("_", " ");
|
||||||
|
fieldMetaData.setName(fieldName);
|
||||||
|
fieldMetaData.setLabel(label);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// in this use case, every field is required and editable //
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
fieldMetaData.setIsRequired(true);
|
||||||
|
fieldMetaData.setIsEditable(true);
|
||||||
|
|
||||||
|
if(defaultValues.containsKey(fieldName))
|
||||||
|
{
|
||||||
|
fieldMetaData.setDefaultValue(defaultValues.get(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldList.add(fieldMetaData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// make output object and return //
|
||||||
|
///////////////////////////////////
|
||||||
|
DynamicFormWidgetData widgetData = new DynamicFormWidgetData();
|
||||||
|
widgetData.setFieldList(fieldList);
|
||||||
|
widgetData.setMergedDynamicFormValuesIntoFieldName("inputValues");
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeIsEmpty(fieldList))
|
||||||
|
{
|
||||||
|
widgetData.setNoFieldsMessage("This Report does not use any Variable Values");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RenderWidgetOutput(widgetData);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error rendering scheduled report values dynamic form widget", e, logPair("queryParams", String.valueOf(input.getQueryParams())));
|
||||||
|
throw (new QException("Error rendering scheduled report values dynamic form widget", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,9 +25,11 @@ package com.kingsrook.qqq.backend.core.model.savedreports;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.DefaultWidgetRenderer;
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.DefaultWidgetRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.common.TimeZonePossibleValueSourceMetaDataProvider;
|
||||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||||
@ -54,6 +56,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RenderSavedReportMetaDataProducer;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RenderSavedReportMetaDataProducer;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RunScheduledReportMetaDataProducer;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -63,8 +66,12 @@ public class SavedReportsMetaDataProvider
|
|||||||
{
|
{
|
||||||
public static final String REPORT_STORAGE_TABLE_NAME = "reportStorage";
|
public static final String REPORT_STORAGE_TABLE_NAME = "reportStorage";
|
||||||
|
|
||||||
|
public static final String SAVED_REPORT_JOIN_SCHEDULED_REPORT = "scheduledReportJoinSavedReport";
|
||||||
public static final String SHARED_SAVED_REPORT_JOIN_SAVED_REPORT = "sharedSavedReportJoinSavedReport";
|
public static final String SHARED_SAVED_REPORT_JOIN_SAVED_REPORT = "sharedSavedReportJoinSavedReport";
|
||||||
|
|
||||||
|
public static final String SCHEDULED_REPORT_VALUES_WIDGET = "scheduledReportValuesWidget";
|
||||||
|
public static final String RENDER_REPORT_PROCESS_VALUES_WIDGET = "renderReportProcessValuesWidget";
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
@ -73,6 +80,7 @@ public class SavedReportsMetaDataProvider
|
|||||||
{
|
{
|
||||||
instance.addTable(defineSavedReportTable(recordTablesBackendName, backendDetailEnricher));
|
instance.addTable(defineSavedReportTable(recordTablesBackendName, backendDetailEnricher));
|
||||||
instance.addTable(defineRenderedReportTable(recordTablesBackendName, backendDetailEnricher));
|
instance.addTable(defineRenderedReportTable(recordTablesBackendName, backendDetailEnricher));
|
||||||
|
|
||||||
instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedReport.TABLE_NAME));
|
instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedReport.TABLE_NAME));
|
||||||
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(ReportFormatPossibleValueEnum.NAME, ReportFormatPossibleValueEnum.values()));
|
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(ReportFormatPossibleValueEnum.NAME, ReportFormatPossibleValueEnum.values()));
|
||||||
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(RenderedReportStatus.NAME, RenderedReportStatus.values()));
|
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(RenderedReportStatus.NAME, RenderedReportStatus.values()));
|
||||||
@ -85,10 +93,30 @@ public class SavedReportsMetaDataProvider
|
|||||||
.filter(f -> RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME.equals(f.getName()))
|
.filter(f -> RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME.equals(f.getName()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.ifPresent(f -> f.setDefaultValue(REPORT_STORAGE_TABLE_NAME));
|
.ifPresent(f -> f.setDefaultValue(REPORT_STORAGE_TABLE_NAME));
|
||||||
|
instance.addWidget(defineRenderReportProcessValuesWidget());
|
||||||
|
|
||||||
instance.addWidget(defineReportSetupWidget());
|
instance.addWidget(defineReportSetupWidget());
|
||||||
instance.addWidget(definePivotTableSetupWidget());
|
instance.addWidget(definePivotTableSetupWidget());
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// todo - param to enable scheduling? //
|
||||||
|
////////////////////////////////////////
|
||||||
|
instance.addTable(defineScheduledReportTable(recordTablesBackendName, backendDetailEnricher));
|
||||||
|
QJoinMetaData join = defineSavedReportJoinScheduledReport();
|
||||||
|
instance.addJoin(join);
|
||||||
|
instance.addWidget(defineScheduledReportJoinSavedReportWidget(join));
|
||||||
|
QProcessMetaData scheduledReportSyncToScheduledJobProcess = new ScheduledReportSyncToScheduledJobProcess().produce(instance);
|
||||||
|
instance.addProcess(scheduledReportSyncToScheduledJobProcess);
|
||||||
|
instance.addWidget(defineScheduledReportValuesWidget());
|
||||||
|
|
||||||
|
QProcessMetaData runScheduledReportProcess = new RunScheduledReportMetaDataProducer().produce(instance);
|
||||||
|
instance.addProcess(runScheduledReportProcess);
|
||||||
|
|
||||||
|
if(instance.getPossibleValueSource(TimeZonePossibleValueSourceMetaDataProvider.NAME) == null)
|
||||||
|
{
|
||||||
|
instance.addPossibleValueSource(new TimeZonePossibleValueSourceMetaDataProvider().produce());
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
// todo - param to enable sharing? //
|
// todo - param to enable sharing? //
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
@ -102,6 +130,64 @@ public class SavedReportsMetaDataProvider
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QWidgetMetaDataInterface defineScheduledReportValuesWidget()
|
||||||
|
{
|
||||||
|
return new QWidgetMetaData()
|
||||||
|
.withName(SCHEDULED_REPORT_VALUES_WIDGET)
|
||||||
|
.withType(WidgetType.DYNAMIC_FORM.getType())
|
||||||
|
.withIsCard(true)
|
||||||
|
.withLabel("Variable Values")
|
||||||
|
.withCodeReference(new QCodeReference(ReportValuesDynamicFormWidgetRenderer.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QWidgetMetaDataInterface defineRenderReportProcessValuesWidget()
|
||||||
|
{
|
||||||
|
return new QWidgetMetaData()
|
||||||
|
.withName(RENDER_REPORT_PROCESS_VALUES_WIDGET)
|
||||||
|
.withType(WidgetType.DYNAMIC_FORM.getType())
|
||||||
|
.withIsCard(false)
|
||||||
|
.withDefaultValue("isEditable", true)
|
||||||
|
.withCodeReference(new QCodeReference(ReportValuesDynamicFormWidgetRenderer.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QJoinMetaData defineSavedReportJoinScheduledReport()
|
||||||
|
{
|
||||||
|
return (new QJoinMetaData()
|
||||||
|
.withName(SAVED_REPORT_JOIN_SCHEDULED_REPORT)
|
||||||
|
.withLeftTable(SavedReport.TABLE_NAME)
|
||||||
|
.withRightTable(ScheduledReport.TABLE_NAME)
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withJoinOn(new JoinOn("id", "savedReportId")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QWidgetMetaDataInterface defineScheduledReportJoinSavedReportWidget(QJoinMetaData join)
|
||||||
|
{
|
||||||
|
return ChildRecordListRenderer.widgetMetaDataBuilder(join)
|
||||||
|
.withLabel("Schedules")
|
||||||
|
.withCanAddChildRecord(true)
|
||||||
|
.getWidgetMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -187,6 +273,7 @@ public class SavedReportsMetaDataProvider
|
|||||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
||||||
.withSection(new QFieldSection("filtersAndColumns", new QIcon().withName("table_chart"), Tier.T2).withLabel("Filters and Columns").withWidgetName("reportSetupWidget"))
|
.withSection(new QFieldSection("filtersAndColumns", new QIcon().withName("table_chart"), Tier.T2).withLabel("Filters and Columns").withWidgetName("reportSetupWidget"))
|
||||||
.withSection(new QFieldSection("pivotTable", new QIcon().withName("pivot_table_chart"), Tier.T2).withLabel("Pivot Table").withWidgetName("pivotTableSetupWidget"))
|
.withSection(new QFieldSection("pivotTable", new QIcon().withName("pivot_table_chart"), Tier.T2).withLabel("Pivot Table").withWidgetName("pivotTableSetupWidget"))
|
||||||
|
.withSection(new QFieldSection("schedule", new QIcon().withName("schedule"), Tier.T2).withWidgetName(SAVED_REPORT_JOIN_SCHEDULED_REPORT))
|
||||||
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("queryFilterJson", "columnsJson", "pivotTableJson")).withIsHidden(true))
|
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("queryFilterJson", "columnsJson", "pivotTableJson")).withIsHidden(true))
|
||||||
.withSection(new QFieldSection("hidden", new QIcon().withName("text_snippet"), Tier.T2, List.of("inputFieldsJson", "userId")).withIsHidden(true))
|
.withSection(new QFieldSection("hidden", new QIcon().withName("text_snippet"), Tier.T2, List.of("inputFieldsJson", "userId")).withIsHidden(true))
|
||||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||||
@ -278,4 +365,40 @@ public class SavedReportsMetaDataProvider
|
|||||||
return (table);
|
return (table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QTableMetaData defineScheduledReportTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||||
|
{
|
||||||
|
QTableMetaData table = new QTableMetaData()
|
||||||
|
.withName(ScheduledReport.TABLE_NAME)
|
||||||
|
.withIcon(new QIcon().withName("schedule_send"))
|
||||||
|
.withRecordLabelFormat("%s (Schedule %s)")
|
||||||
|
.withRecordLabelFields("savedReportId", "id")
|
||||||
|
.withBackendName(backendName)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withFieldsFromEntity(ScheduledReport.class)
|
||||||
|
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "savedReportId")))
|
||||||
|
.withSection(new QFieldSection("settings", new QIcon().withName("settings"), Tier.T2, List.of("cronExpression", "cronTimeZoneId", "isActive", "format")))
|
||||||
|
.withSection(new QFieldSection("recipient", new QIcon().withName("email"), Tier.T2, List.of("toAddresses", "subject")))
|
||||||
|
.withSection(new QFieldSection("variableValues", new QIcon().withName("data_object"), Tier.T2).withWidgetName(SCHEDULED_REPORT_VALUES_WIDGET))
|
||||||
|
.withSection(new QFieldSection("hidden", new QIcon().withName("visibility_off"), Tier.T2, List.of("inputValues")).withIsHidden(true))
|
||||||
|
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||||
|
|
||||||
|
if(backendDetailEnricher != null)
|
||||||
|
{
|
||||||
|
backendDetailEnricher.accept(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(ScheduledReportTableCustomizer.class));
|
||||||
|
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(ScheduledReportTableCustomizer.class));
|
||||||
|
table.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(ScheduledReportTableCustomizer.class));
|
||||||
|
table.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(ScheduledReportTableCustomizer.class));
|
||||||
|
table.withCustomizer(TableCustomizers.POST_DELETE_RECORD, new QCodeReference(ScheduledReportTableCustomizer.class));
|
||||||
|
|
||||||
|
return (table);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.common.TimeZonePossibleValueSourceMetaDataProvider;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Entity bean for the scheduled report table
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ScheduledReport extends QRecordEntity
|
||||||
|
{
|
||||||
|
public static final String TABLE_NAME = "scheduledReport";
|
||||||
|
|
||||||
|
@QField(isEditable = false)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@QField(isEditable = false)
|
||||||
|
private Instant createDate;
|
||||||
|
|
||||||
|
@QField(isEditable = false)
|
||||||
|
private Instant modifyDate;
|
||||||
|
|
||||||
|
@QField(isRequired = true, possibleValueSourceName = SavedReport.TABLE_NAME)
|
||||||
|
private Integer savedReportId;
|
||||||
|
|
||||||
|
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, isRequired = true)
|
||||||
|
private String cronExpression;
|
||||||
|
|
||||||
|
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, possibleValueSourceName = TimeZonePossibleValueSourceMetaDataProvider.NAME, isRequired = true)
|
||||||
|
private String cronTimeZoneId;
|
||||||
|
|
||||||
|
@QField(isRequired = true, defaultValue = "true")
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@QField(isRequired = true)
|
||||||
|
private String toAddresses;
|
||||||
|
|
||||||
|
@QField(isRequired = true, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
@QField(isRequired = true, maxLength = 20, valueTooLongBehavior = ValueTooLongBehavior.ERROR, possibleValueSourceName = ReportFormatPossibleValueEnum.NAME)
|
||||||
|
private String format;
|
||||||
|
|
||||||
|
@QField()
|
||||||
|
private String inputValues;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport(QRecord qRecord) throws QException
|
||||||
|
{
|
||||||
|
populateFromQRecord(qRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for id
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getId()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for id
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setId(Integer id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for createDate
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Instant getCreateDate()
|
||||||
|
{
|
||||||
|
return createDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for createDate
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCreateDate(Instant createDate)
|
||||||
|
{
|
||||||
|
this.createDate = createDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for modifyDate
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Instant getModifyDate()
|
||||||
|
{
|
||||||
|
return modifyDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for modifyDate
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setModifyDate(Instant modifyDate)
|
||||||
|
{
|
||||||
|
this.modifyDate = modifyDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for id
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withId(Integer id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for createDate
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withCreateDate(Instant createDate)
|
||||||
|
{
|
||||||
|
this.createDate = createDate;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for modifyDate
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withModifyDate(Instant modifyDate)
|
||||||
|
{
|
||||||
|
this.modifyDate = modifyDate;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for savedReportId
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getSavedReportId()
|
||||||
|
{
|
||||||
|
return (this.savedReportId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for savedReportId
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSavedReportId(Integer savedReportId)
|
||||||
|
{
|
||||||
|
this.savedReportId = savedReportId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for savedReportId
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withSavedReportId(Integer savedReportId)
|
||||||
|
{
|
||||||
|
this.savedReportId = savedReportId;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for cronExpression
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getCronExpression()
|
||||||
|
{
|
||||||
|
return (this.cronExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for cronExpression
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCronExpression(String cronExpression)
|
||||||
|
{
|
||||||
|
this.cronExpression = cronExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for cronExpression
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withCronExpression(String cronExpression)
|
||||||
|
{
|
||||||
|
this.cronExpression = cronExpression;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for cronTimeZoneId
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getCronTimeZoneId()
|
||||||
|
{
|
||||||
|
return (this.cronTimeZoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for cronTimeZoneId
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCronTimeZoneId(String cronTimeZoneId)
|
||||||
|
{
|
||||||
|
this.cronTimeZoneId = cronTimeZoneId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for cronTimeZoneId
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withCronTimeZoneId(String cronTimeZoneId)
|
||||||
|
{
|
||||||
|
this.cronTimeZoneId = cronTimeZoneId;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for isActive
|
||||||
|
*******************************************************************************/
|
||||||
|
public Boolean getIsActive()
|
||||||
|
{
|
||||||
|
return (this.isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for isActive
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIsActive(Boolean isActive)
|
||||||
|
{
|
||||||
|
this.isActive = isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for isActive
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withIsActive(Boolean isActive)
|
||||||
|
{
|
||||||
|
this.isActive = isActive;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for toAddresses
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getToAddresses()
|
||||||
|
{
|
||||||
|
return (this.toAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for toAddresses
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setToAddresses(String toAddresses)
|
||||||
|
{
|
||||||
|
this.toAddresses = toAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for toAddresses
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withToAddresses(String toAddresses)
|
||||||
|
{
|
||||||
|
this.toAddresses = toAddresses;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSubject()
|
||||||
|
{
|
||||||
|
return (this.subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSubject(String subject)
|
||||||
|
{
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for subject
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withSubject(String subject)
|
||||||
|
{
|
||||||
|
this.subject = subject;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for format
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getFormat()
|
||||||
|
{
|
||||||
|
return (this.format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for format
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFormat(String format)
|
||||||
|
{
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for format
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withFormat(String format)
|
||||||
|
{
|
||||||
|
this.format = format;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getInputValues()
|
||||||
|
{
|
||||||
|
return (this.inputValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setInputValues(String inputValues)
|
||||||
|
{
|
||||||
|
this.inputValues = inputValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledReport withInputValues(String inputValues)
|
||||||
|
{
|
||||||
|
this.inputValues = inputValues;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
||||||
|
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.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.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobParameter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RunScheduledReportMetaDataProducer;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.tablesync.AbstractTableSyncTransformStep;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.tablesync.TableSyncProcess;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ScheduledReportSyncToScheduledJobProcess extends AbstractTableSyncTransformStep implements MetaDataProducerInterface<QProcessMetaData>
|
||||||
|
{
|
||||||
|
public static final String NAME = "scheduledReportSyncToScheduledJob";
|
||||||
|
|
||||||
|
public static final String SCHEDULER_NAME_FIELD_NAME = "schedulerName";
|
||||||
|
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ScheduledReportSyncToScheduledJobProcess.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||||
|
{
|
||||||
|
QProcessMetaData processMetaData = TableSyncProcess.processMetaDataBuilder(false)
|
||||||
|
.withName(NAME)
|
||||||
|
.withTableName(ScheduledReport.TABLE_NAME)
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// todo - maybe - to keep 'em in sync? //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
//.withBasepullConfiguration(CoreMetaDataProvider.getDefaultBasepullConfiguration("modifyDate", ONE_DAY_IN_HOURS)
|
||||||
|
// .withSecondsToSubtractFromLastRunTimeForTimestampQuery(10 * 60))
|
||||||
|
// .withSchedule(new QScheduleMetaData()
|
||||||
|
// .withRepeatSeconds(SYNC_BASEPULLS_INTERVAL_SECONDS))
|
||||||
|
|
||||||
|
.withSyncTransformStepClass(getClass())
|
||||||
|
.withReviewStepRecordFields(List.of(
|
||||||
|
new QFieldMetaData("savedReportId", QFieldType.INTEGER).withPossibleValueSourceName(SavedReport.TABLE_NAME),
|
||||||
|
new QFieldMetaData("cronExpression", QFieldType.STRING),
|
||||||
|
new QFieldMetaData("isActive", QFieldType.BOOLEAN),
|
||||||
|
new QFieldMetaData("toAddresses", QFieldType.STRING),
|
||||||
|
new QFieldMetaData("subject", QFieldType.STRING),
|
||||||
|
new QFieldMetaData("format", QFieldType.STRING).withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME)
|
||||||
|
))
|
||||||
|
.getProcessMetaData();
|
||||||
|
|
||||||
|
processMetaData.getBackendStep(StreamedETLWithFrontendProcess.STEP_NAME_PREVIEW).getInputMetaData()
|
||||||
|
.withField(new QFieldMetaData(SCHEDULER_NAME_FIELD_NAME, QFieldType.STRING));
|
||||||
|
|
||||||
|
return (processMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QRecord populateRecordToStore(RunBackendStepInput runBackendStepInput, QRecord destinationRecord, QRecord sourceRecord) throws QException
|
||||||
|
{
|
||||||
|
ScheduledReport scheduledReport = new ScheduledReport(sourceRecord);
|
||||||
|
ScheduledJob scheduledJob;
|
||||||
|
|
||||||
|
if(destinationRecord == null || destinationRecord.getValue("id") == null)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// need to do an insert - set lots of key values in the scheduled job //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
scheduledJob = new ScheduledJob();
|
||||||
|
scheduledJob.setLabel("Scheduled Report " + scheduledReport.getId());
|
||||||
|
scheduledJob.setDescription("Job to run Scheduled Report Id " + scheduledReport.getId()
|
||||||
|
+ " (which runs Report Id " + scheduledReport.getSavedReportId() + ")");
|
||||||
|
scheduledJob.setSchedulerName(runBackendStepInput.getValueString(SCHEDULER_NAME_FIELD_NAME));
|
||||||
|
scheduledJob.setType(ScheduledJobType.PROCESS.name());
|
||||||
|
scheduledJob.setForeignKeyType(getScheduledJobForeignKeyType());
|
||||||
|
scheduledJob.setForeignKeyValue(String.valueOf(scheduledReport.getId()));
|
||||||
|
scheduledJob.setJobParameters(List.of(
|
||||||
|
new ScheduledJobParameter().withKey("processName").withValue(getProcessNameScheduledJobParameter()),
|
||||||
|
new ScheduledJobParameter().withKey("recordId").withValue(ValueUtils.getValueAsString(scheduledReport.getId()))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// else doing an update - populate scheduled job entity from destination record //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
scheduledJob = new ScheduledJob(destinationRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// these fields sync on insert and update //
|
||||||
|
// todo - if no diffs, should we return null (to avoid changing quartz at all?) //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
scheduledJob.setCronExpression(scheduledReport.getCronExpression());
|
||||||
|
scheduledJob.setCronTimeZoneId(scheduledReport.getCronTimeZoneId());
|
||||||
|
scheduledJob.setIsActive(scheduledReport.getIsActive());
|
||||||
|
|
||||||
|
return scheduledJob.toQRecord();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
static String getScheduledJobForeignKeyType()
|
||||||
|
{
|
||||||
|
return "scheduledReport";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static String getProcessNameScheduledJobParameter()
|
||||||
|
{
|
||||||
|
return RunScheduledReportMetaDataProducer.NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected QQueryFilter getExistingRecordQueryFilter(RunBackendStepInput runBackendStepInput, List<Serializable> sourceKeyList)
|
||||||
|
{
|
||||||
|
return super.getExistingRecordQueryFilter(runBackendStepInput, sourceKeyList)
|
||||||
|
.withCriteria(new QFilterCriteria("foreignKeyType", QCriteriaOperator.EQUALS, getScheduledJobForeignKeyType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected SyncProcessConfig getSyncProcessConfig()
|
||||||
|
{
|
||||||
|
return new SyncProcessConfig(ScheduledReport.TABLE_NAME, "id", ScheduledJob.TABLE_NAME, "foreignKeyValue", true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
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.tables.delete.DeleteInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||||
|
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.scheduledjobs.ScheduledJob;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import org.quartz.CronScheduleBuilder;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ScheduledReportTableCustomizer implements TableCustomizerInterface
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ScheduledReportTableCustomizer.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||||
|
{
|
||||||
|
preInsertOrUpdate(records);
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||||
|
{
|
||||||
|
preInsertOrUpdate(records);
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void preInsertOrUpdate(List<QRecord> records)
|
||||||
|
{
|
||||||
|
for(QRecord record : records)
|
||||||
|
{
|
||||||
|
String cronExpression = record.getValueString("cronExpression");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CronScheduleBuilder.cronScheduleNonvalidatedExpression(cronExpression);
|
||||||
|
}
|
||||||
|
catch(ParseException e)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Cron Expression [" + cronExpression + "] is not valid: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||||
|
{
|
||||||
|
runSyncProcess(records);
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||||
|
{
|
||||||
|
runSyncProcess(records);
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void runSyncProcess(List<QRecord> records)
|
||||||
|
{
|
||||||
|
List<Serializable> scheduledReportIds = records.stream()
|
||||||
|
.filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors()))
|
||||||
|
.map(r -> r.getValue("id")).toList();
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeIsEmpty(scheduledReportIds))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RunProcessInput runProcessInput = new RunProcessInput();
|
||||||
|
runProcessInput.setProcessName(ScheduledReportSyncToScheduledJobProcess.NAME);
|
||||||
|
runProcessInput.setCallback(QProcessCallbackFactory.forPrimaryKeys("id", scheduledReportIds));
|
||||||
|
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||||
|
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||||
|
|
||||||
|
Serializable processSummary = runProcessOutput.getValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY);
|
||||||
|
System.out.println(processSummary);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error syncing scheduled reports to scheduled jobs", e, logPair("scheduledReportIds", scheduledReportIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||||
|
{
|
||||||
|
List<String> scheduledReportIds = records.stream()
|
||||||
|
.filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors()))
|
||||||
|
.map(r -> r.getValueString("id")).toList();
|
||||||
|
|
||||||
|
if(scheduledReportIds.isEmpty())
|
||||||
|
{
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
// delete any corresponding scheduledJob records //
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeleteOutput deleteOutput = new DeleteAction().execute(new DeleteInput(ScheduledJob.TABLE_NAME).withQueryFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("foreignKeyType", QCriteriaOperator.EQUALS, ScheduledReportSyncToScheduledJobProcess.getScheduledJobForeignKeyType()))
|
||||||
|
.withCriteria(new QFilterCriteria("foreignKeyValue", QCriteriaOperator.IN, scheduledReportIds))
|
||||||
|
));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error deleting scheduled jobs for scheduled reports", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
}
|
@ -26,22 +26,15 @@ import java.util.List;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.DeleteSavedViewProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.DeleteSavedViewProcess;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.QuerySavedViewProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.QuerySavedViewProcess;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.StoreSavedViewProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.StoreSavedViewProcess;
|
||||||
@ -52,7 +45,6 @@ import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.Store
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class SavedViewsMetaDataProvider
|
public class SavedViewsMetaDataProvider
|
||||||
{
|
{
|
||||||
public static final String SHARED_SAVED_VIEW_JOIN_SAVED_VIEW = "sharedSavedViewJoinSavedView";
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -65,16 +57,6 @@ public class SavedViewsMetaDataProvider
|
|||||||
instance.addProcess(QuerySavedViewProcess.getProcessMetaData());
|
instance.addProcess(QuerySavedViewProcess.getProcessMetaData());
|
||||||
instance.addProcess(StoreSavedViewProcess.getProcessMetaData());
|
instance.addProcess(StoreSavedViewProcess.getProcessMetaData());
|
||||||
instance.addProcess(DeleteSavedViewProcess.getProcessMetaData());
|
instance.addProcess(DeleteSavedViewProcess.getProcessMetaData());
|
||||||
|
|
||||||
/////////////////////////////////////
|
|
||||||
// todo - param to enable sharing? //
|
|
||||||
/////////////////////////////////////
|
|
||||||
instance.addTable(defineSharedSavedViewTable(backendName, backendDetailEnricher));
|
|
||||||
instance.addJoin(defineSharedSavedViewJoinSavedView());
|
|
||||||
if(instance.getPossibleValueSource(ShareScopePossibleValueMetaDataProducer.NAME) == null)
|
|
||||||
{
|
|
||||||
instance.addPossibleValueSource(new ShareScopePossibleValueMetaDataProducer().produce(new QInstance()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -122,50 +104,4 @@ public class SavedViewsMetaDataProvider
|
|||||||
.withOrderByField("label");
|
.withOrderByField("label");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QTableMetaData defineSharedSavedViewTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
|
||||||
{
|
|
||||||
QTableMetaData table = new QTableMetaData()
|
|
||||||
.withName(SharedSavedView.TABLE_NAME)
|
|
||||||
.withLabel("Shared View")
|
|
||||||
.withIcon(new QIcon().withName("share"))
|
|
||||||
.withRecordLabelFormat("%s")
|
|
||||||
.withRecordLabelFields("savedViewId")
|
|
||||||
.withBackendName(backendName)
|
|
||||||
.withUniqueKey(new UniqueKey("savedViewId", "userId"))
|
|
||||||
.withPrimaryKeyField("id")
|
|
||||||
.withFieldsFromEntity(SharedSavedView.class)
|
|
||||||
// todo - security key
|
|
||||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
|
|
||||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "savedViewId", "userId")))
|
|
||||||
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("scope")))
|
|
||||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
|
||||||
|
|
||||||
if(backendDetailEnricher != null)
|
|
||||||
{
|
|
||||||
backendDetailEnricher.accept(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (table);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private QJoinMetaData defineSharedSavedViewJoinSavedView()
|
|
||||||
{
|
|
||||||
return (new QJoinMetaData()
|
|
||||||
.withName(SHARED_SAVED_VIEW_JOIN_SAVED_VIEW)
|
|
||||||
.withLeftTable(SharedSavedView.TABLE_NAME)
|
|
||||||
.withRightTable(SavedView.TABLE_NAME)
|
|
||||||
.withType(JoinType.MANY_TO_ONE)
|
|
||||||
.withJoinOn(new JoinOn("savedViewId", "id")));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,12 @@ public class ScheduledJob extends QRecordEntity
|
|||||||
@QField(isRequired = true)
|
@QField(isRequired = true)
|
||||||
private Boolean isActive;
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||||
|
private String foreignKeyType;
|
||||||
|
|
||||||
|
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||||
|
private String foreignKeyValue;
|
||||||
|
|
||||||
@QAssociation(name = ScheduledJobParameter.TABLE_NAME)
|
@QAssociation(name = ScheduledJobParameter.TABLE_NAME)
|
||||||
private List<ScheduledJobParameter> jobParameters;
|
private List<ScheduledJobParameter> jobParameters;
|
||||||
|
|
||||||
@ -493,4 +499,65 @@ public class ScheduledJob extends QRecordEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for foreignKeyType
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getForeignKeyType()
|
||||||
|
{
|
||||||
|
return (this.foreignKeyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for foreignKeyType
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setForeignKeyType(String foreignKeyType)
|
||||||
|
{
|
||||||
|
this.foreignKeyType = foreignKeyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for foreignKeyType
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledJob withForeignKeyType(String foreignKeyType)
|
||||||
|
{
|
||||||
|
this.foreignKeyType = foreignKeyType;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for foreignKeyValue
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getForeignKeyValue()
|
||||||
|
{
|
||||||
|
return (this.foreignKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for foreignKeyValue
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setForeignKeyValue(String foreignKeyValue)
|
||||||
|
{
|
||||||
|
this.foreignKeyValue = foreignKeyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for foreignKeyValue
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScheduledJob withForeignKeyValue(String foreignKeyValue)
|
||||||
|
{
|
||||||
|
this.foreignKeyValue = foreignKeyValue;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ public class ScheduledJobsMetaDataProvider
|
|||||||
.withRecordLabelFields("label")
|
.withRecordLabelFields("label")
|
||||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "description")))
|
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "description")))
|
||||||
.withSection(new QFieldSection("schedule", new QIcon().withName("alarm"), Tier.T2, List.of("cronExpression", "cronTimeZoneId", "repeatSeconds")))
|
.withSection(new QFieldSection("schedule", new QIcon().withName("alarm"), Tier.T2, List.of("cronExpression", "cronTimeZoneId", "repeatSeconds")))
|
||||||
.withSection(new QFieldSection("settings", new QIcon().withName("tune"), Tier.T2, List.of("type", "isActive", "schedulerName")))
|
.withSection(new QFieldSection("settings", new QIcon().withName("tune"), Tier.T2, List.of("type", "isActive", "schedulerName", "foreignKeyType", "foreignKeyValue")))
|
||||||
.withSection(new QFieldSection("parameters", new QIcon().withName("list"), Tier.T2).withWidgetName(JOB_PARAMETER_JOIN_NAME))
|
.withSection(new QFieldSection("parameters", new QIcon().withName("list"), Tier.T2).withWidgetName(JOB_PARAMETER_JOIN_NAME))
|
||||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import java.util.Optional;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
@ -58,6 +59,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
||||||
{
|
{
|
||||||
|
private QBackendTransaction transaction = null;
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
@ -65,6 +67,7 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
@Override
|
@Override
|
||||||
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||||
{
|
{
|
||||||
|
transaction = insertInput.getTransaction();
|
||||||
validateConditionalFields(records, Collections.emptyMap());
|
validateConditionalFields(records, Collections.emptyMap());
|
||||||
return (records);
|
return (records);
|
||||||
}
|
}
|
||||||
@ -77,6 +80,7 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
@Override
|
@Override
|
||||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
|
transaction = insertInput.getTransaction();
|
||||||
scheduleJobsForRecordList(records);
|
scheduleJobsForRecordList(records);
|
||||||
return (records);
|
return (records);
|
||||||
}
|
}
|
||||||
@ -89,6 +93,7 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
@Override
|
@Override
|
||||||
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||||
{
|
{
|
||||||
|
transaction = updateInput.getTransaction();
|
||||||
Map<Integer, QRecord> freshOldRecordsWithAssociationsMap = CollectionUtils.recordsToMap(freshlyQueryForRecordsWithAssociations(oldRecordList.get()), "id", Integer.class);
|
Map<Integer, QRecord> freshOldRecordsWithAssociationsMap = CollectionUtils.recordsToMap(freshlyQueryForRecordsWithAssociations(oldRecordList.get()), "id", Integer.class);
|
||||||
|
|
||||||
validateConditionalFields(records, freshOldRecordsWithAssociationsMap);
|
validateConditionalFields(records, freshOldRecordsWithAssociationsMap);
|
||||||
@ -169,6 +174,7 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
@Override
|
@Override
|
||||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||||
{
|
{
|
||||||
|
transaction = updateInput.getTransaction();
|
||||||
if(oldRecordList.isPresent())
|
if(oldRecordList.isPresent())
|
||||||
{
|
{
|
||||||
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
||||||
@ -201,6 +207,7 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
@Override
|
@Override
|
||||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
|
transaction = deleteInput.getTransaction();
|
||||||
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
||||||
unscheduleJobsForRecordList(records, idsWithErrors);
|
unscheduleJobsForRecordList(records, idsWithErrors);
|
||||||
return (records);
|
return (records);
|
||||||
@ -262,12 +269,13 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static List<QRecord> freshlyQueryForRecordsWithAssociations(List<QRecord> records) throws QException
|
private List<QRecord> freshlyQueryForRecordsWithAssociations(List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
List<Integer> idList = records.stream().map(r -> r.getValueInteger("id")).toList();
|
List<Integer> idList = records.stream().map(r -> r.getValueInteger("id")).toList();
|
||||||
|
|
||||||
return new QueryAction().execute(new QueryInput(ScheduledJob.TABLE_NAME)
|
return new QueryAction().execute(new QueryInput(ScheduledJob.TABLE_NAME)
|
||||||
.withIncludeAssociations(true)
|
.withIncludeAssociations(true)
|
||||||
|
.withTransaction(transaction)
|
||||||
.withFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, idList))))
|
.withFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, idList))))
|
||||||
.getRecords();
|
.getRecords();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.modules.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface MessagingProviderInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
String getType();
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.modules.messaging;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** This class is responsible for loading a messaging provider, by its name, and
|
||||||
|
** returning an instance.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QMessagingProviderDispatcher
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(QMessagingProviderDispatcher.class);
|
||||||
|
|
||||||
|
private static Map<String, String> typeToProviderClassNameMap;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QMessagingProviderDispatcher()
|
||||||
|
{
|
||||||
|
initBackendTypeToModuleClassNameMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void initBackendTypeToModuleClassNameMap()
|
||||||
|
{
|
||||||
|
if(typeToProviderClassNameMap != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> newMap = new HashMap<>();
|
||||||
|
|
||||||
|
typeToProviderClassNameMap = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void registerMessagingProvider(MessagingProviderInterface messagingProviderInstance)
|
||||||
|
{
|
||||||
|
initBackendTypeToModuleClassNameMap();
|
||||||
|
String type = messagingProviderInstance.getType();
|
||||||
|
if(typeToProviderClassNameMap.containsKey(type))
|
||||||
|
{
|
||||||
|
LOG.info("Overwriting messagingProvider type [" + type + "] with [" + messagingProviderInstance.getClass() + "]");
|
||||||
|
}
|
||||||
|
typeToProviderClassNameMap.put(type, messagingProviderInstance.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public MessagingProviderInterface getMessagingProviderInterface(QMessagingProviderMetaData messagingProviderMetaData) throws QModuleDispatchException
|
||||||
|
{
|
||||||
|
return (getMessagingProviderInterface(messagingProviderMetaData.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public MessagingProviderInterface getMessagingProviderInterface(String type) throws QModuleDispatchException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String className = typeToProviderClassNameMap.get(type);
|
||||||
|
if(className == null)
|
||||||
|
{
|
||||||
|
throw (new QModuleDispatchException("Unrecognized messaging provider type [" + type + "] in dispatcher."));
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> moduleClass = Class.forName(className);
|
||||||
|
return (MessagingProviderInterface) moduleClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
catch(QModuleDispatchException qmde)
|
||||||
|
{
|
||||||
|
throw (qmde);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QModuleDispatchException("Error getting messaging provider of type: " + type, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,15 +29,27 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.messaging.SendMessageAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
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.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
||||||
@ -53,8 +65,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
|||||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReportStatus;
|
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReportStatus;
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.apache.commons.validator.EmailValidator;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
@ -80,12 +94,29 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
|||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// read inputs, set up params //
|
// read inputs, set up params //
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
String sesProviderName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.SES_PROVIDER_NAME);
|
||||||
|
String fromEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FROM_EMAIL_ADDRESS);
|
||||||
|
String replyToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.REPLY_TO_EMAIL_ADDRESS);
|
||||||
String storageTableName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME);
|
String storageTableName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME);
|
||||||
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT));
|
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT));
|
||||||
|
String sendToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS);
|
||||||
|
String emailSubject = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_SUBJECT);
|
||||||
SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0));
|
SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0));
|
||||||
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
|
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
|
||||||
String storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
|
String storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
|
||||||
OutputStream outputStream = new StorageAction().createOutputStream(new StorageInput(storageTableName).withReference(storageReference));
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if sending an email (or emails), validate the addresses before doing anything so user gets error and can fix //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
List<String> toEmailAddressList = new ArrayList<>();
|
||||||
|
if(StringUtils.hasContent(sendToEmailAddress))
|
||||||
|
{
|
||||||
|
toEmailAddressList = validateEmailAddresses(sendToEmailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageAction storageAction = new StorageAction();
|
||||||
|
StorageInput storageInput = new StorageInput(storageTableName).withReference(storageReference);
|
||||||
|
OutputStream outputStream = storageAction.createOutputStream(storageInput);
|
||||||
|
|
||||||
LOG.info("Starting to render a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference));
|
LOG.info("Starting to render a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference));
|
||||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
||||||
@ -116,6 +147,10 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
|||||||
.withReportFormat(reportFormat)
|
.withReportFormat(reportFormat)
|
||||||
.withReportOutputStream(outputStream));
|
.withReportOutputStream(outputStream));
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// todo variable-values //
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
Map<String, Serializable> values = runBackendStepInput.getValues();
|
Map<String, Serializable> values = runBackendStepInput.getValues();
|
||||||
reportInput.setInputValues(values);
|
reportInput.setInputValues(values);
|
||||||
|
|
||||||
@ -132,10 +167,41 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
|||||||
.withValue("rowCount", reportOutput.getTotalRecordCount())
|
.withValue("rowCount", reportOutput.getTotalRecordCount())
|
||||||
));
|
));
|
||||||
|
|
||||||
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + "." + reportFormat.getExtension());
|
String downloadFileName = downloadFileBaseName + "." + reportFormat.getExtension();
|
||||||
|
runBackendStepOutput.addValue("downloadFileName", downloadFileName);
|
||||||
runBackendStepOutput.addValue("storageTableName", storageTableName);
|
runBackendStepOutput.addValue("storageTableName", storageTableName);
|
||||||
runBackendStepOutput.addValue("storageReference", storageReference);
|
runBackendStepOutput.addValue("storageReference", storageReference);
|
||||||
LOG.info("Completed rendering a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference), logPair("rowCount", reportOutput.getTotalRecordCount()));
|
LOG.info("Completed rendering a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference), logPair("rowCount", reportOutput.getTotalRecordCount()));
|
||||||
|
|
||||||
|
if(!toEmailAddressList.isEmpty() && CollectionUtils.nullSafeHasContents(QContext.getQInstance().getMessagingProviders()))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
// since sending email, make s3 file publicly accessible //
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
storageAction.makePublic(storageInput);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// add multiparty in case multiple recipients //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
MultiParty recipients = new MultiParty();
|
||||||
|
for(String toAddress : toEmailAddressList)
|
||||||
|
{
|
||||||
|
recipients.addParty(new Party().withAddress(toAddress).withRole(EmailPartyRole.TO));
|
||||||
|
}
|
||||||
|
|
||||||
|
String downloadURL = storageAction.getDownloadURL(storageInput);
|
||||||
|
new SendMessageAction().execute(new SendMessageInput()
|
||||||
|
.withMessagingProviderName(sesProviderName)
|
||||||
|
.withTo(recipients)
|
||||||
|
.withFrom(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress(fromEmailAddress).withRole(EmailPartyRole.FROM))
|
||||||
|
.withParty(new Party().withAddress(replyToEmailAddress).withRole(EmailPartyRole.REPLY_TO))
|
||||||
|
)
|
||||||
|
.withSubject(StringUtils.hasContent(emailSubject) ? emailSubject : downloadFileBaseName)
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("To download your report, open this URL in your browser: " + downloadURL))
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("Link: <a target=\"_blank\" href=\"" + downloadURL + "\" download>" + downloadFileName + "</a>"))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@ -156,6 +222,42 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private List<String> validateEmailAddresses(String sendToEmailAddress) throws QUserFacingException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// split email address string on spaces, comma, and semicolon //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
List<String> toEmailAddressList = Arrays.asList(sendToEmailAddress.split("[\\s,;]+"));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// check each address keeping track of any bad ones //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
List<String> invalidEmails = new ArrayList<>();
|
||||||
|
EmailValidator validator = EmailValidator.getInstance();
|
||||||
|
for(String emailAddress : toEmailAddressList)
|
||||||
|
{
|
||||||
|
if(!validator.isValid(emailAddress))
|
||||||
|
{
|
||||||
|
invalidEmails.add(emailAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////
|
||||||
|
// if bad one found, throw exception //
|
||||||
|
///////////////////////////////////////
|
||||||
|
if(!invalidEmails.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QUserFacingException("The following email addresses were invalid: " + StringUtils.join(",", invalidEmails)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (toEmailAddressList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -38,6 +38,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -47,8 +48,13 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
|
|||||||
{
|
{
|
||||||
public static final String NAME = "renderSavedReport";
|
public static final String NAME = "renderSavedReport";
|
||||||
|
|
||||||
|
public static final String SES_PROVIDER_NAME = "sesProviderName";
|
||||||
|
public static final String FROM_EMAIL_ADDRESS = "fromEmailAddress";
|
||||||
|
public static final String REPLY_TO_EMAIL_ADDRESS = "replyToEmailAddress";
|
||||||
public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName";
|
public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName";
|
||||||
public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat";
|
public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat";
|
||||||
|
public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress";
|
||||||
|
public static final String FIELD_NAME_EMAIL_SUBJECT = "emailSubject";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -63,23 +69,34 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
|
|||||||
.withLabel("Render Report")
|
.withLabel("Render Report")
|
||||||
.withTableName(SavedReport.TABLE_NAME)
|
.withTableName(SavedReport.TABLE_NAME)
|
||||||
.withIcon(new QIcon().withName("print"))
|
.withIcon(new QIcon().withName("print"))
|
||||||
|
|
||||||
.addStep(new QBackendStepMetaData()
|
.addStep(new QBackendStepMetaData()
|
||||||
.withName("pre")
|
.withName("pre")
|
||||||
.withInputData(new QFunctionInputMetaData()
|
.withInputData(new QFunctionInputMetaData()
|
||||||
|
.withField(new QFieldMetaData(SES_PROVIDER_NAME, QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData(FROM_EMAIL_ADDRESS, QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData(REPLY_TO_EMAIL_ADDRESS, QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData(FIELD_NAME_STORAGE_TABLE_NAME, QFieldType.STRING))
|
.withField(new QFieldMetaData(FIELD_NAME_STORAGE_TABLE_NAME, QFieldType.STRING))
|
||||||
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
|
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
|
||||||
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))
|
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))
|
||||||
|
|
||||||
.addStep(new QFrontendStepMetaData()
|
.addStep(new QFrontendStepMetaData()
|
||||||
.withName("input")
|
.withName("input")
|
||||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
|
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
|
||||||
.withFormField(new QFieldMetaData(FIELD_NAME_REPORT_FORMAT, QFieldType.STRING)
|
.withFormField(new QFieldMetaData(FIELD_NAME_REPORT_FORMAT, QFieldType.STRING)
|
||||||
.withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME)
|
.withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME)
|
||||||
.withIsRequired(true)))
|
.withIsRequired(true))
|
||||||
|
.withFormField(new QFieldMetaData(FIELD_NAME_EMAIL_ADDRESS, QFieldType.STRING).withLabel("Send To Email Address"))
|
||||||
|
.withFormField(new QFieldMetaData(FIELD_NAME_EMAIL_SUBJECT, QFieldType.STRING).withLabel("Email Subject"))
|
||||||
|
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.WIDGET)
|
||||||
|
.withValue("widgetName", SavedReportsMetaDataProvider.RENDER_REPORT_PROCESS_VALUES_WIDGET)))
|
||||||
|
|
||||||
.addStep(new QBackendStepMetaData()
|
.addStep(new QBackendStepMetaData()
|
||||||
.withName("execute")
|
.withName("execute")
|
||||||
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
|
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
|
||||||
.withTableName(SavedReport.TABLE_NAME)))
|
.withTableName(SavedReport.TABLE_NAME)))
|
||||||
.withCode(new QCodeReference(RenderSavedReportExecuteStep.class)))
|
.withCode(new QCodeReference(RenderSavedReportExecuteStep.class)))
|
||||||
|
|
||||||
.addStep(new QFrontendStepMetaData()
|
.addStep(new QFrontendStepMetaData()
|
||||||
.withName("output")
|
.withName("output")
|
||||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM)));
|
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM)));
|
||||||
|
@ -63,17 +63,20 @@ public class RenderSavedReportPreStep implements BackendStep
|
|||||||
List<QRecord> records = runBackendStepInput.getRecords();
|
List<QRecord> records = runBackendStepInput.getRecords();
|
||||||
if(!CollectionUtils.nullSafeHasContents(records))
|
if(!CollectionUtils.nullSafeHasContents(records))
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("No report was selected or found to be rendered."));
|
throw (new QUserFacingException("No report was selected or found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(records.size() > 1)
|
if(records.size() > 1)
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("You may only render 1 report at a time."));
|
throw (new QUserFacingException("You may only run 1 report at a time."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// put the savedReportId in values - this'll get passed into the widget, so it knows //
|
||||||
|
// what report we're working with, and thus what inputs to prompt for //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
SavedReport savedReport = new SavedReport(records.get(0));
|
SavedReport savedReport = new SavedReport(records.get(0));
|
||||||
|
runBackendStepOutput.addValue("savedReportId", savedReport.getId());
|
||||||
// todo - check for inputs - set up the input screen...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
|
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.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.savedreports.ScheduledReport;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class RunScheduledReportExecuteStep implements BackendStep
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(RunScheduledReportExecuteStep.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||||
|
{
|
||||||
|
Integer scheduledReportId = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<QRecord> records = runBackendStepInput.getRecords();
|
||||||
|
if(!CollectionUtils.nullSafeHasContents(records))
|
||||||
|
{
|
||||||
|
throw (new QUserFacingException("No scheduled report was selected or found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduledReport scheduledReport = new ScheduledReport(records.get(0));
|
||||||
|
scheduledReportId = scheduledReport.getId();
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// run the process that renders the report //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
RunProcessAction runProcessAction = new RunProcessAction();
|
||||||
|
RunProcessInput renderProcessInput = new RunProcessInput();
|
||||||
|
renderProcessInput.setProcessName(RenderSavedReportMetaDataProducer.NAME);
|
||||||
|
renderProcessInput.setCallback(QProcessCallbackFactory.forPrimaryKey("id", scheduledReport.getSavedReportId()));
|
||||||
|
renderProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||||
|
renderProcessInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
|
||||||
|
|
||||||
|
renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, scheduledReport.getFormat());
|
||||||
|
renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS, scheduledReport.getToAddresses());
|
||||||
|
renderProcessInput.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_SUBJECT, scheduledReport.getSubject());
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(scheduledReport.getInputValues()))
|
||||||
|
{
|
||||||
|
//////////////////////////
|
||||||
|
// todo variable-values //
|
||||||
|
//////////////////////////
|
||||||
|
}
|
||||||
|
|
||||||
|
RunProcessOutput renderProcessOutput = runProcessAction.execute(renderProcessInput);
|
||||||
|
}
|
||||||
|
catch(QUserFacingException ufe)
|
||||||
|
{
|
||||||
|
LOG.info("Error running scheduled report", ufe, logPair("id", scheduledReportId));
|
||||||
|
throw (ufe);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error running scheduled report", e, logPair("id", scheduledReportId));
|
||||||
|
throw (new QException("Error running scheduled report", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.savedreports.ScheduledReport;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** define process for rendering scheduled reports - that is - a thin layer on
|
||||||
|
** top of rendering a saved report.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class RunScheduledReportMetaDataProducer implements MetaDataProducerInterface<QProcessMetaData>
|
||||||
|
{
|
||||||
|
public static final String NAME = "runScheduledReport";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||||
|
{
|
||||||
|
QProcessMetaData process = new QProcessMetaData()
|
||||||
|
.withName(NAME)
|
||||||
|
.withLabel("Run Scheduled Report")
|
||||||
|
.withTableName(ScheduledReport.TABLE_NAME)
|
||||||
|
.withIcon(new QIcon().withName("print"))
|
||||||
|
|
||||||
|
.addStep(new QBackendStepMetaData()
|
||||||
|
.withName("execute")
|
||||||
|
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
|
||||||
|
.withTableName(ScheduledReport.TABLE_NAME)))
|
||||||
|
.withCode(new QCodeReference(RunScheduledReportExecuteStep.class)))
|
||||||
|
|
||||||
|
.addStep(new QFrontendStepMetaData()
|
||||||
|
.withName("results")
|
||||||
|
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||||
|
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Success")))); // todo!!!
|
||||||
|
|
||||||
|
return (process);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -43,8 +44,10 @@ 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.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.VariantRunStrategy;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.VariantRunStrategy;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||||
|
|
||||||
|
|
||||||
@ -138,13 +141,27 @@ public class SchedulerUtils
|
|||||||
RunProcessInput runProcessInput = new RunProcessInput();
|
RunProcessInput runProcessInput = new RunProcessInput();
|
||||||
runProcessInput.setProcessName(process.getName());
|
runProcessInput.setProcessName(process.getName());
|
||||||
|
|
||||||
|
Serializable recordId = null;
|
||||||
for(Map.Entry<String, Serializable> entry : CollectionUtils.nonNullMap(processInputValues).entrySet())
|
for(Map.Entry<String, Serializable> entry : CollectionUtils.nonNullMap(processInputValues).entrySet())
|
||||||
{
|
{
|
||||||
runProcessInput.withValue(entry.getKey(), entry.getValue());
|
runProcessInput.withValue(entry.getKey(), entry.getValue());
|
||||||
|
if(entry.getKey().equals("recordId"))
|
||||||
|
{
|
||||||
|
recordId = entry.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if there was a "recordId" input value, and this table is for a process, then set up a callback to get the record //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(recordId != null && StringUtils.hasContent(process.getTableName()))
|
||||||
|
{
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(process.getTableName());
|
||||||
|
runProcessInput.setCallback(QProcessCallbackFactory.forPrimaryKey(table.getPrimaryKeyField(), recordId));
|
||||||
|
}
|
||||||
|
|
||||||
QContext.pushAction(runProcessInput);
|
QContext.pushAction(runProcessInput);
|
||||||
|
|
||||||
RunProcessAction runProcessAction = new RunProcessAction();
|
RunProcessAction runProcessAction = new RunProcessAction();
|
||||||
|
@ -244,7 +244,6 @@ public class JsonUtils
|
|||||||
.registerModule(new JavaTimeModule())
|
.registerModule(new JavaTimeModule())
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
||||||
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
|
||||||
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
|
||||||
/* todo - some future version we may need to do inclusion/exclusion lists like this:
|
/* todo - some future version we may need to do inclusion/exclusion lists like this:
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.messaging.SendMessageAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.OS;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for EmailMessagingProvider
|
||||||
|
*******************************************************************************/
|
||||||
|
class EmailMessagingProviderTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
@DisabledOnOs(OS.LINUX)
|
||||||
|
void test() throws QException
|
||||||
|
{
|
||||||
|
new SendMessageAction().execute(new SendMessageInput()
|
||||||
|
.withMessagingProviderName(TestUtils.EMAIL_MESSAGING_PROVIDER_NAME)
|
||||||
|
.withTo(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff").withRole(EmailPartyRole.TO))
|
||||||
|
.withParty(new Party().withAddress("james.maes@kingsrook.com").withLabel("Mames Maes").withRole(EmailPartyRole.CC))
|
||||||
|
.withParty(new Party().withAddress("tyler.samples@kingsrook.com").withLabel("Tylers Ample").withRole(EmailPartyRole.BCC))
|
||||||
|
)
|
||||||
|
.withFrom(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff"))
|
||||||
|
.withSubject("This is another qqq test message.")
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("This is a text body"))
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("This <u>is</u> an <b>HTML</b> body!"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.messaging.ses;
|
||||||
|
|
||||||
|
|
||||||
|
import cloud.localstack.ServiceName;
|
||||||
|
import cloud.localstack.docker.LocalstackDockerExtension;
|
||||||
|
import cloud.localstack.docker.annotation.LocalstackDockerProperties;
|
||||||
|
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Destination;
|
||||||
|
import com.amazonaws.services.simpleemail.model.Message;
|
||||||
|
import com.amazonaws.services.simpleemail.model.VerifyEmailAddressRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for SendSESAction
|
||||||
|
*******************************************************************************/
|
||||||
|
@ExtendWith(LocalstackDockerExtension.class)
|
||||||
|
@LocalstackDockerProperties(useSingleDockerContainer = true, services = { ServiceName.SES }, portEdge = "2960", portElasticSearch = "2961", imageTag = "1.4")
|
||||||
|
class SendSESActionTest extends BaseTest
|
||||||
|
{
|
||||||
|
public static final String TEST_TO_EMAIL_ADDRESS = "tim-to@coldtrack.com";
|
||||||
|
public static final String TEST_FROM_EMAIL_ADDRESS = "tim-from@coldtrack.com";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Before each unit test, get the test bucket into a known state
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach()
|
||||||
|
{
|
||||||
|
AmazonSimpleEmailService amazonSES = getAmazonSES();
|
||||||
|
amazonSES.verifyEmailAddress(new VerifyEmailAddressRequest().withEmailAddress(TEST_TO_EMAIL_ADDRESS));
|
||||||
|
amazonSES.verifyEmailAddress(new VerifyEmailAddressRequest().withEmailAddress(TEST_FROM_EMAIL_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Access a localstack-configured SES client.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected AmazonSimpleEmailService getAmazonSES()
|
||||||
|
{
|
||||||
|
return (cloud.localstack.awssdkv1.TestUtils.getClientSES());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSendSES() throws QException
|
||||||
|
{
|
||||||
|
SendMessageInput sendMessageInput = new SendMessageInput()
|
||||||
|
.withMessagingProviderName(TestUtils.SES_MESSAGING_PROVIDER_NAME)
|
||||||
|
.withTo(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress(TEST_TO_EMAIL_ADDRESS).withLabel("Test TO").withRole(EmailPartyRole.TO))
|
||||||
|
)
|
||||||
|
.withFrom(new Party().withAddress(TEST_FROM_EMAIL_ADDRESS).withLabel("Test FROM"))
|
||||||
|
.withSubject("This is another qqq test message.")
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("This is a text body"))
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("This <u>is</u> an <b>HTML</b> body!"));
|
||||||
|
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
sendSESAction.setAmazonSES(getAmazonSES());
|
||||||
|
sendSESAction.sendMessage(sendMessageInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGetPartyListFromParty() throws QException
|
||||||
|
{
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
assertEquals(0, sendSESAction.getPartyListFromParty(null).size());
|
||||||
|
assertEquals(1, sendSESAction.getPartyListFromParty(new Party()).size());
|
||||||
|
assertEquals(4, sendSESAction.getPartyListFromParty(
|
||||||
|
new MultiParty()
|
||||||
|
.withParty(new Party())
|
||||||
|
.withParty(new Party())
|
||||||
|
.withParty(new Party())
|
||||||
|
.withParty(new Party())
|
||||||
|
).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGetSource() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
// assert exception if no from given or one without a FROM //
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
assertThatThrownBy(() -> sendSESAction.getSource(new SendMessageInput())).isInstanceOf(QException.class).hasMessageContaining("not provided");
|
||||||
|
assertThatThrownBy(() -> sendSESAction.getSource(new SendMessageInput().withFrom(new Party().withRole(EmailPartyRole.REPLY_TO)))).isInstanceOf(QException.class).hasMessageContaining("not provided");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// should only be one source, and should be the first one in multi party since multiple not supported //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
SendMessageInput multiPartyInput = new SendMessageInput().withFrom(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress("test1").withRole(EmailPartyRole.REPLY_TO))
|
||||||
|
.withParty(new Party().withAddress("test2").withRole(EmailPartyRole.FROM))
|
||||||
|
.withParty(new Party().withAddress("test3").withRole(EmailPartyRole.REPLY_TO))
|
||||||
|
.withParty(new Party().withAddress("test4").withRole(EmailPartyRole.FROM))
|
||||||
|
);
|
||||||
|
assertEquals("test2", sendSESAction.getSource(multiPartyInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGetReplyTos() throws QException
|
||||||
|
{
|
||||||
|
SendMessageInput multiPartyInput = new SendMessageInput()
|
||||||
|
.withFrom(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress("test1").withRole(EmailPartyRole.REPLY_TO))
|
||||||
|
.withParty(new Party().withAddress("test2").withRole(EmailPartyRole.FROM))
|
||||||
|
.withParty(new Party().withAddress("test3").withRole(EmailPartyRole.REPLY_TO))
|
||||||
|
.withParty(new Party().withAddress("test4").withRole(EmailPartyRole.FROM))
|
||||||
|
);
|
||||||
|
SendMessageInput singleInput = new SendMessageInput()
|
||||||
|
.withFrom(new Party().withAddress("test1").withRole(EmailPartyRole.FROM));
|
||||||
|
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
assertEquals(2, sendSESAction.getReplyTos(multiPartyInput).size());
|
||||||
|
assertEquals(0, sendSESAction.getReplyTos(singleInput).size());
|
||||||
|
assertEquals(0, sendSESAction.getReplyTos(null).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBuildDestination() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// assert exception if no tos provided //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
assertThatThrownBy(() -> sendSESAction.buildDestination(new SendMessageInput())).isInstanceOf(QException.class).hasMessageContaining("were provided");
|
||||||
|
assertThatThrownBy(() -> sendSESAction.buildDestination(new SendMessageInput().withTo(new Party().withAddress("test1").withRole(EmailPartyRole.CC)))).isInstanceOf(QException.class).hasMessageContaining("were provided");
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// exception if a FROM given in to field //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> sendSESAction.buildDestination(new SendMessageInput().withTo(new Party().withAddress("test1").withRole(EmailPartyRole.FROM)))).isInstanceOf(QException.class).hasMessageContaining("unrecognized");
|
||||||
|
|
||||||
|
SendMessageInput multiPartyInput = new SendMessageInput()
|
||||||
|
.withTo(new MultiParty()
|
||||||
|
.withParty(new Party().withAddress("test1").withRole(EmailPartyRole.CC))
|
||||||
|
.withParty(new Party().withAddress("test2").withRole(EmailPartyRole.TO))
|
||||||
|
.withParty(new Party().withAddress("test3").withRole(EmailPartyRole.BCC))
|
||||||
|
.withParty(new Party().withAddress("test4").withRole(EmailPartyRole.BCC))
|
||||||
|
.withParty(new Party().withAddress("test5").withRole(EmailPartyRole.TO))
|
||||||
|
.withParty(new Party().withAddress("test6").withRole(EmailPartyRole.TO))
|
||||||
|
.withParty(new Party().withAddress("test7").withRole(EmailPartyRole.TO))
|
||||||
|
);
|
||||||
|
Destination destination = sendSESAction.buildDestination(multiPartyInput);
|
||||||
|
assertEquals(2, destination.getBccAddresses().size());
|
||||||
|
assertEquals(4, destination.getToAddresses().size());
|
||||||
|
assertEquals(1, destination.getCcAddresses().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBuildMessage() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// assert exception if no tos provided //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
SendSESAction sendSESAction = new SendSESAction();
|
||||||
|
assertThatThrownBy(() -> sendSESAction.buildMessage(new SendMessageInput())).isInstanceOf(QException.class).hasMessageContaining("'Text' nor an 'HTML'");
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// exception if a FROM given in to field //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> sendSESAction.buildDestination(new SendMessageInput().withTo(new Party().withAddress("test1").withRole(EmailPartyRole.FROM)))).isInstanceOf(QException.class).hasMessageContaining("unrecognized");
|
||||||
|
|
||||||
|
String htmlContent = "HTML_CONTENT";
|
||||||
|
String textContent = "TEXT_CONTENT";
|
||||||
|
String subject = "SUBJECT";
|
||||||
|
SendMessageInput input = new SendMessageInput()
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody(htmlContent))
|
||||||
|
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody(textContent))
|
||||||
|
.withSubject(subject);
|
||||||
|
Message message = sendSESAction.buildMessage(input);
|
||||||
|
assertEquals(htmlContent, message.getBody().getHtml().getData());
|
||||||
|
assertEquals(textContent, message.getBody().getText().getData());
|
||||||
|
assertEquals(subject, message.getSubject().getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -74,6 +74,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.email.EmailMessagingProviderMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.messaging.ses.SESMessagingProviderMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
@ -180,6 +183,9 @@ public class TestUtils
|
|||||||
public static final String SECURITY_KEY_TYPE_STORE_NULL_BEHAVIOR = "storeNullBehavior";
|
public static final String SECURITY_KEY_TYPE_STORE_NULL_BEHAVIOR = "storeNullBehavior";
|
||||||
public static final String SECURITY_KEY_TYPE_INTERNAL_OR_EXTERNAL = "internalOrExternal";
|
public static final String SECURITY_KEY_TYPE_INTERNAL_OR_EXTERNAL = "internalOrExternal";
|
||||||
|
|
||||||
|
public static final String EMAIL_MESSAGING_PROVIDER_NAME = "email";
|
||||||
|
public static final String SES_MESSAGING_PROVIDER_NAME = "ses";
|
||||||
|
|
||||||
public static final String SIMPLE_SCHEDULER_NAME = "simpleScheduler";
|
public static final String SIMPLE_SCHEDULER_NAME = "simpleScheduler";
|
||||||
public static final String TEST_SQS_QUEUE = "testSQSQueue";
|
public static final String TEST_SQS_QUEUE = "testSQSQueue";
|
||||||
|
|
||||||
@ -242,6 +248,9 @@ public class TestUtils
|
|||||||
qInstance.addQueueProvider(defineSqsProvider());
|
qInstance.addQueueProvider(defineSqsProvider());
|
||||||
qInstance.addQueue(defineTestSqsQueue());
|
qInstance.addQueue(defineTestSqsQueue());
|
||||||
|
|
||||||
|
qInstance.addMessagingProvider(defineEmailMessagingProvider());
|
||||||
|
qInstance.addMessagingProvider(defineSESMessagingProvider());
|
||||||
|
|
||||||
defineWidgets(qInstance);
|
defineWidgets(qInstance);
|
||||||
defineApps(qInstance);
|
defineApps(qInstance);
|
||||||
|
|
||||||
@ -252,6 +261,37 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QMessagingProviderMetaData defineSESMessagingProvider()
|
||||||
|
{
|
||||||
|
String accessKey = "MOCK"; // interpreter.interpret("${env.SES_ACCESS_KEY}");
|
||||||
|
String secretKey = "MOCK"; // interpreter.interpret("${env.SES_SECRET_KEY}");
|
||||||
|
String region = "MOCK"; // interpreter.interpret("${env.SES_REGION}");
|
||||||
|
|
||||||
|
return (new SESMessagingProviderMetaData()
|
||||||
|
.withAccessKey(accessKey)
|
||||||
|
.withSecretKey(secretKey)
|
||||||
|
.withRegion(region)
|
||||||
|
.withName(SES_MESSAGING_PROVIDER_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QMessagingProviderMetaData defineEmailMessagingProvider()
|
||||||
|
{
|
||||||
|
return new EmailMessagingProviderMetaData()
|
||||||
|
.withSmtpServer("localhost")
|
||||||
|
.withSmtpPort("2500")
|
||||||
|
.withName(EMAIL_MESSAGING_PROVIDER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -100,4 +100,15 @@ public class FilesystemStorageAction extends AbstractFilesystemAction implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
return ("file://" + getFullPath(storageInput));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import java.io.File;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import com.amazonaws.services.s3.AmazonS3;
|
import com.amazonaws.services.s3.AmazonS3;
|
||||||
|
import com.amazonaws.services.s3.model.CannedAccessControlList;
|
||||||
import com.amazonaws.services.s3.model.GetObjectRequest;
|
import com.amazonaws.services.s3.model.GetObjectRequest;
|
||||||
import com.amazonaws.services.s3.model.S3Object;
|
import com.amazonaws.services.s3.model.S3Object;
|
||||||
import com.amazonaws.services.s3.model.S3ObjectInputStream;
|
import com.amazonaws.services.s3.model.S3ObjectInputStream;
|
||||||
@ -111,7 +112,53 @@ public class S3StorageAction extends AbstractS3Action implements QStorageInterfa
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw (new QException("Exception getting s3 input stream for file", e));
|
throw (new QException("Exception getting s3 input stream for file.", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
S3BackendMetaData backend = (S3BackendMetaData) storageInput.getBackend();
|
||||||
|
preAction(backend);
|
||||||
|
|
||||||
|
AmazonS3 amazonS3 = getS3Utils().getAmazonS3();
|
||||||
|
String fullPath = getFullPath(storageInput);
|
||||||
|
return (amazonS3.getUrl(backend.getBucketName(), fullPath).toString());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Exception getting the S3 download URL.", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void makePublic(StorageInput storageInput) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
S3BackendMetaData backend = (S3BackendMetaData) storageInput.getBackend();
|
||||||
|
preAction(backend);
|
||||||
|
|
||||||
|
AmazonS3 amazonS3 = getS3Utils().getAmazonS3();
|
||||||
|
String fullPath = getFullPath(storageInput);
|
||||||
|
amazonS3.setObjectAcl(backend.getBucketName(), fullPath, CannedAccessControlList.PublicRead);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Exception making s3 file publicly available.", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,10 +353,8 @@ public class SharingTest
|
|||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// now see if you can update to a user that you don't have (you can't!) //
|
// now see if you can update to a user that you don't have (you can't!) //
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
/* todo - here's where the logic in ValidateRecordSecurityLockHelper fails us...
|
|
||||||
updateOutput = new UpdateAction().execute(new UpdateInput(SharedAsset.TABLE_NAME).withRecord(makeRecordToUpdate.get().withValue("userId", 2)));
|
updateOutput = new UpdateAction().execute(new UpdateInput(SharedAsset.TABLE_NAME).withRecord(makeRecordToUpdate.get().withValue("userId", 2)));
|
||||||
assertThat(updateOutput.getRecords().get(0).getErrors()).isNotEmpty();
|
assertThat(updateOutput.getRecords().get(0).getErrors()).isNotEmpty();
|
||||||
*/
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
// Add that user (2) to the session - then the update should succeed //
|
// Add that user (2) to the session - then the update should succeed //
|
||||||
|
Reference in New Issue
Block a user