Compare commits

..

5 Commits

55 changed files with 388 additions and 4412 deletions

View File

@ -54,7 +54,7 @@
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
<coverage.haltOnFailure>true</coverage.haltOnFailure>
<coverage.instructionCoveredRatioMinimum>0.75</coverage.instructionCoveredRatioMinimum>
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
<plugin.shade.phase>none</plugin.shade.phase>
</properties>

View File

@ -173,19 +173,6 @@
<version>1.12.321</version>
</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>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
@ -199,12 +186,6 @@
<version>2.23.0</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Common deps for all qqq modules -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
@ -60,7 +59,6 @@ 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.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -68,9 +66,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/
public class ChildRecordListRenderer extends AbstractWidgetRenderer
{
private static final QLogger LOG = QLogger.getLogger(ChildRecordListRenderer.class);
/*******************************************************************************
**
@ -177,134 +172,126 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
try
String widgetLabel = input.getQueryParams().get("widgetLabel");
String joinName = input.getQueryParams().get("joinName");
QJoinMetaData join = input.getInstance().getJoin(joinName);
String id = input.getQueryParams().get("id");
QTableMetaData leftTable = input.getInstance().getTable(join.getLeftTable());
QTableMetaData rightTable = input.getInstance().getTable(join.getRightTable());
Integer maxRows = null;
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
{
String widgetLabel = input.getQueryParams().get("widgetLabel");
String joinName = input.getQueryParams().get("joinName");
QJoinMetaData join = input.getInstance().getJoin(joinName);
String id = input.getQueryParams().get("id");
QTableMetaData leftTable = input.getInstance().getTable(join.getLeftTable());
QTableMetaData rightTable = input.getInstance().getTable(join.getRightTable());
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
}
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
{
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"));
}
Integer maxRows = null;
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fetch the record that we're getting children for. e.g., the left-side of the join, with the input id //
// but - only try this if we were given an id. note, this widget could be called for on an INSERT screen, where we don't have a record yet //
// but we still want to be able to return all the other data in here that otherwise comes from the widget meta data, join, etc. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int totalRows = 0;
QRecord primaryRecord = null;
QQueryFilter filter = null;
QueryOutput queryOutput = new QueryOutput(new QueryInput());
if(StringUtils.hasContent(id))
{
GetInput getInput = new GetInput();
getInput.setTableName(join.getLeftTable());
getInput.setPrimaryKey(id);
GetOutput getOutput = new GetAction().execute(getInput);
primaryRecord = getOutput.getRecord();
if(primaryRecord == null)
{
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
}
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
{
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"));
throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fetch the record that we're getting children for. e.g., the left-side of the join, with the input id //
// but - only try this if we were given an id. note, this widget could be called for on an INSERT screen, where we don't have a record yet //
// but we still want to be able to return all the other data in here that otherwise comes from the widget meta data, join, etc. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int totalRows = 0;
QRecord primaryRecord = null;
QQueryFilter filter = null;
QueryOutput queryOutput = new QueryOutput(new QueryInput());
if(StringUtils.hasContent(id))
////////////////////////////////////////////////////////////////////
// set up the query - for the table on the right side of the join //
////////////////////////////////////////////////////////////////////
filter = new QQueryFilter();
for(JoinOn joinOn : join.getJoinOns())
{
GetInput getInput = new GetInput();
getInput.setTableName(join.getLeftTable());
getInput.setPrimaryKey(id);
GetOutput getOutput = new GetAction().execute(getInput);
primaryRecord = getOutput.getRecord();
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField()))));
}
filter.setOrderBys(join.getOrderBys());
filter.setLimit(maxRows);
if(primaryRecord == null)
{
throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id));
}
QueryInput queryInput = new QueryInput();
queryInput.setTableName(join.getRightTable());
queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true);
queryInput.setFilter(filter);
queryOutput = new QueryAction().execute(queryInput);
////////////////////////////////////////////////////////////////////
// set up the query - for the table on the right side of the join //
////////////////////////////////////////////////////////////////////
filter = new QQueryFilter();
QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords());
totalRows = queryOutput.getRecords().size();
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
{
/////////////////////////////////////////////////////////////////////////////////////
// if the input said to only do some max, and the # of results we got is that max, //
// then do a count query, for displaying 1-n of <count> //
/////////////////////////////////////////////////////////////////////////////////////
CountInput countInput = new CountInput();
countInput.setTableName(join.getRightTable());
countInput.setFilter(filter);
totalRows = new CountAction().execute(countInput).getCount();
}
}
String tablePath = input.getInstance().getTablePath(rightTable.getName());
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("canAddChildRecord"))))
{
widgetData.setCanAddChildRecord(true);
//////////////////////////////////////////////////////////
// new child records must have values from the join-ons //
//////////////////////////////////////////////////////////
Map<String, Serializable> defaultValuesForNewChildRecords = new HashMap<>();
if(primaryRecord != null)
{
for(JoinOn joinOn : join.getJoinOns())
{
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField()))));
}
filter.setOrderBys(join.getOrderBys());
filter.setLimit(maxRows);
QueryInput queryInput = new QueryInput();
queryInput.setTableName(join.getRightTable());
queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true);
queryInput.setFilter(filter);
queryOutput = new QueryAction().execute(queryInput);
QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords());
totalRows = queryOutput.getRecords().size();
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
{
/////////////////////////////////////////////////////////////////////////////////////
// if the input said to only do some max, and the # of results we got is that max, //
// then do a count query, for displaying 1-n of <count> //
/////////////////////////////////////////////////////////////////////////////////////
CountInput countInput = new CountInput();
countInput.setTableName(join.getRightTable());
countInput.setFilter(filter);
totalRows = new CountAction().execute(countInput).getCount();
defaultValuesForNewChildRecords.put(joinOn.getRightField(), primaryRecord.getValue(joinOn.getLeftField()));
}
}
String tablePath = input.getInstance().getTablePath(rightTable.getName());
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords);
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("canAddChildRecord"))))
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
if(widgetValues.containsKey("disabledFieldsForNewChildRecords"))
{
widgetData.setCanAddChildRecord(true);
//////////////////////////////////////////////////////////
// new child records must have values from the join-ons //
//////////////////////////////////////////////////////////
Map<String, Serializable> defaultValuesForNewChildRecords = new HashMap<>();
if(primaryRecord != null)
widgetData.setDisabledFieldsForNewChildRecords((Set<String>) widgetValues.get("disabledFieldsForNewChildRecords"));
}
else
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there are no disabled fields specified - then normally any fields w/ a default value get implicitly disabled //
// but - if we didn't look-up the primary record, then we'll want to explicit disable fields from joins //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(primaryRecord == null)
{
Set<String> implicitlyDisabledFields = new HashSet<>();
widgetData.setDisabledFieldsForNewChildRecords(implicitlyDisabledFields);
for(JoinOn joinOn : join.getJoinOns())
{
defaultValuesForNewChildRecords.put(joinOn.getRightField(), primaryRecord.getValue(joinOn.getLeftField()));
}
}
widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords);
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
if(widgetValues.containsKey("disabledFieldsForNewChildRecords"))
{
widgetData.setDisabledFieldsForNewChildRecords((Set<String>) widgetValues.get("disabledFieldsForNewChildRecords"));
}
else
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there are no disabled fields specified - then normally any fields w/ a default value get implicitly disabled //
// but - if we didn't look-up the primary record, then we'll want to explicit disable fields from joins //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(primaryRecord == null)
{
Set<String> implicitlyDisabledFields = new HashSet<>();
widgetData.setDisabledFieldsForNewChildRecords(implicitlyDisabledFields);
for(JoinOn joinOn : join.getJoinOns())
{
implicitlyDisabledFields.add(joinOn.getRightField());
}
implicitlyDisabledFields.add(joinOn.getRightField());
}
}
}
}
return (new RenderWidgetOutput(widgetData));
}
catch(Exception e)
{
LOG.warn("Error rendering child record list", e, logPair("widgetName", () -> input.getWidgetMetaData().getName()));
throw (e);
}
return (new RenderWidgetOutput(widgetData));
}
}

View File

@ -30,7 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
/*******************************************************************************
** Interface for actions that a backend can perform, based on streaming data
** into the backend's storage.
** into the backend's storage.
*******************************************************************************/
public interface QStorageInterface
{
@ -46,24 +46,4 @@ public interface QStorageInterface
*******************************************************************************/
InputStream getInputStream(StorageInput storageInput) throws QException;
/*******************************************************************************
**
*******************************************************************************/
default void makePublic(StorageInput storageInput) throws QException
{
//////////
// noop //
//////////
}
/*******************************************************************************
**
*******************************************************************************/
default String getDownloadURL(StorageInput storageInput) throws QException
{
return (null);
}
}

View File

@ -1,64 +0,0 @@
/*
* 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));
}
}

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -115,14 +114,4 @@ public class QProcessCallbackFactory
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))));
}
}

View File

@ -169,7 +169,6 @@ public class QueryAction
nextLevelQueryInput.setTableName(association.getAssociatedTableName());
nextLevelQueryInput.setIncludeAssociations(true);
nextLevelQueryInput.setAssociationNamesToInclude(buildNextLevelAssociationNamesToInclude(association.getName(), queryInput.getAssociationNamesToInclude()));
nextLevelQueryInput.setTransaction(queryInput.getTransaction());
QQueryFilter filter = new QQueryFilter();
nextLevelQueryInput.setFilter(filter);

View File

@ -93,28 +93,4 @@ public class StorageAction
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
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));
}
}

View File

@ -403,10 +403,13 @@ public class ValidateRecordSecurityLockHelper
if(action.equals(Action.UPDATE))
{
////////////////////////////////////////////////////////
// when doing an update, convert all OR's to AND's... //
////////////////////////////////////////////////////////
updateOperators(locksOfType, MultiRecordSecurityLock.BooleanOperator.AND);
////////////////////////////////////////////////////////////////////////////
// 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);
}
////////////////////////////////////////

View File

@ -1,95 +0,0 @@
/*
* 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);
}
}

View File

@ -1,95 +0,0 @@
/*
* 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);
}
}

View File

@ -1,39 +0,0 @@
/*
* 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
}
}

View File

@ -1,92 +0,0 @@
/*
* 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);
}
}

View File

@ -1,127 +0,0 @@
/*
* 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);
}
}

View File

@ -1,39 +0,0 @@
/*
* 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
}
}

View File

@ -1,33 +0,0 @@
/*
* 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
{
}

View File

@ -1,35 +0,0 @@
/*
* 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
}

View File

@ -1,38 +0,0 @@
/*
* 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
}

View File

@ -417,4 +417,20 @@ public class QueryJoin
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "QueryJoin{base="
+ baseTableOrAlias + ", joinTable='"
+ joinTable + ", joinMetaData="
+ (joinMetaData == null ? null : joinMetaData.getName()) + ", alias='"
+ alias + ", select="
+ select + ", type="
+ type + '}';
}
}

View File

@ -1,150 +0,0 @@
/*
* 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);
}
}

View File

@ -27,12 +27,10 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
*******************************************************************************/
public enum WidgetType
{
///////////////////////////////////
// (generally) dashboard widgets //
///////////////////////////////////
ALERT("alert"),
BAR_CHART("barChart"),
CHART("chart"),
CHILD_RECORD_LIST("childRecordList"),
DIVIDER("divider"),
FIELD_VALUE_LIST("fieldValueList"),
GENERIC("generic"),
@ -42,34 +40,20 @@ public enum WidgetType
SMALL_LINE_CHART("smallLineChart"),
LOCATION("location"),
MULTI_STATISTICS("multiStatistics"),
PARENT_WIDGET("parentWidget"),
PIE_CHART("pieChart"),
PROCESS("process"),
QUICK_SIGHT_CHART("quickSightChart"),
STATISTICS("statistics"),
STACKED_BAR_CHART("stackedBarChart"),
STEPPER("stepper"),
TABLE("table"),
USA_MAP("usaMap"),
///////////////////////////////
// widget to house a process //
///////////////////////////////
PROCESS("process"),
///////////////////////
// container widgets //
///////////////////////
PARENT_WIDGET("parentWidget"),
COMPOSITE("composite"),
//////////////////////////////
// record view/edit widgets //
//////////////////////////////
CHILD_RECORD_LIST("childRecordList"),
DYNAMIC_FORM("dynamicForm"),
DATA_BAG_VIEWER("dataBagViewer"),
PIVOT_TABLE_SETUP("pivotTableSetup"),
SCRIPT_VIEWER("scriptViewer"),
REPORT_SETUP("reportSetup"),
SCRIPT_VIEWER("scriptViewer");
PIVOT_TABLE_SETUP("pivotTableSetup");
private final String type;

View File

@ -46,7 +46,6 @@ 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.joins.QJoinMetaData;
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.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@ -79,7 +78,6 @@ public class QInstance
private QAuthenticationMetaData authentication = null;
private QBrandingMetaData branding = null;
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. //
@ -741,53 +739,6 @@ 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
**

View File

@ -1,110 +0,0 @@
/*
* 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);
}
}

View File

@ -1,56 +0,0 @@
/*
* 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);
}
}

View File

@ -1,116 +0,0 @@
/*
* 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);
}
}

View File

@ -1,216 +0,0 @@
/*
* 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;
}
}

View File

@ -1,56 +0,0 @@
/*
* 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);
}
}

View File

@ -1,148 +0,0 @@
/*
* 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);
}
}

View File

@ -1,335 +0,0 @@
/*
* 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;
}
}

View File

@ -36,7 +36,6 @@ public enum QComponentType
RECORD_LIST,
PROCESS_SUMMARY_RESULTS,
GOOGLE_DRIVE_SELECT_FOLDER,
WIDGET,
HTML;
///////////////////////////////////////////////////////////////////////////
// keep these values in sync with QComponentType.ts in qqq-frontend-core //

View File

@ -1,175 +0,0 @@
/*
* 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));
}
}
}

View File

@ -25,11 +25,9 @@ package com.kingsrook.qqq.backend.core.model.savedreports;
import java.util.List;
import java.util.function.Consumer;
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.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.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
@ -56,7 +54,6 @@ 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.UniqueKey;
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RenderSavedReportMetaDataProducer;
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RunScheduledReportMetaDataProducer;
/*******************************************************************************
@ -66,12 +63,8 @@ public class SavedReportsMetaDataProvider
{
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 SCHEDULED_REPORT_VALUES_WIDGET = "scheduledReportValuesWidget";
public static final String RENDER_REPORT_PROCESS_VALUES_WIDGET = "renderReportProcessValuesWidget";
/*******************************************************************************
**
@ -80,7 +73,6 @@ public class SavedReportsMetaDataProvider
{
instance.addTable(defineSavedReportTable(recordTablesBackendName, backendDetailEnricher));
instance.addTable(defineRenderedReportTable(recordTablesBackendName, backendDetailEnricher));
instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedReport.TABLE_NAME));
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(ReportFormatPossibleValueEnum.NAME, ReportFormatPossibleValueEnum.values()));
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(RenderedReportStatus.NAME, RenderedReportStatus.values()));
@ -93,30 +85,10 @@ public class SavedReportsMetaDataProvider
.filter(f -> RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME.equals(f.getName()))
.findFirst()
.ifPresent(f -> f.setDefaultValue(REPORT_STORAGE_TABLE_NAME));
instance.addWidget(defineRenderReportProcessValuesWidget());
instance.addWidget(defineReportSetupWidget());
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? //
/////////////////////////////////////
@ -130,64 +102,6 @@ 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();
}
/*******************************************************************************
**
*******************************************************************************/
@ -273,7 +187,6 @@ public class SavedReportsMetaDataProvider
.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("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("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")));
@ -365,40 +278,4 @@ public class SavedReportsMetaDataProvider
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);
}
}

View File

@ -1,443 +0,0 @@
/*
* 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);
}
}

View File

@ -1,189 +0,0 @@
/*
* 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);
}
}

View File

@ -1,197 +0,0 @@
/*
* 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);
}
}

View File

@ -26,15 +26,22 @@ import java.util.List;
import java.util.function.Consumer;
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.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.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.possiblevalues.PVSValueFormatAndFields;
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.sharing.ShareScopePossibleValueMetaDataProducer;
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.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.QuerySavedViewProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.StoreSavedViewProcess;
@ -45,6 +52,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.Store
*******************************************************************************/
public class SavedViewsMetaDataProvider
{
public static final String SHARED_SAVED_VIEW_JOIN_SAVED_VIEW = "sharedSavedViewJoinSavedView";
/*******************************************************************************
@ -57,6 +65,16 @@ public class SavedViewsMetaDataProvider
instance.addProcess(QuerySavedViewProcess.getProcessMetaData());
instance.addProcess(StoreSavedViewProcess.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()));
}
}
@ -104,4 +122,50 @@ public class SavedViewsMetaDataProvider
.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")));
}
}

View File

@ -19,259 +19,246 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.messaging;
package com.kingsrook.qqq.backend.core.model.savedviews;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.sharing.ShareScopePossibleValueMetaDataProducer;
/*******************************************************************************
**
** Entity bean for the shared saved view table
*******************************************************************************/
public class SendMessageInput extends AbstractActionInput
public class SharedSavedView extends QRecordEntity
{
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);
}
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 SendMessageInput withContent(Content content)
public SharedSavedView()
{
addContent(content);
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public SharedSavedView(QRecord qRecord) throws QException
{
populateFromQRecord(qRecord);
}
/*******************************************************************************
** 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);
}
/*******************************************************************************
**
** Getter for createDate
*******************************************************************************/
public void addContent(Content content)
public Instant getCreateDate()
{
if(this.contentList == null)
{
this.contentList = new ArrayList<>();
}
this.contentList.add(content);
return (this.createDate);
}
/*******************************************************************************
**
** Setter for createDate
*******************************************************************************/
public SendMessageInput withAttachment(Attachment attachment)
public void setCreateDate(Instant createDate)
{
addAttachment(attachment);
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate
*******************************************************************************/
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
**
** Getter for modifyDate
*******************************************************************************/
public void addAttachment(Attachment attachment)
public Instant getModifyDate()
{
if(this.attachmentList == null)
{
this.attachmentList = new ArrayList<>();
}
this.attachmentList.add(attachment);
return (this.modifyDate);
}
/*******************************************************************************
** Getter for messagingProviderName
** Setter for modifyDate
*******************************************************************************/
public String getMessagingProviderName()
public void setModifyDate(Instant modifyDate)
{
return (this.messagingProviderName);
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Setter for messagingProviderName
** Fluent setter for modifyDate
*******************************************************************************/
public void setMessagingProviderName(String messagingProviderName)
public com.kingsrook.qqq.backend.core.model.savedviews.SharedSavedView withModifyDate(Instant modifyDate)
{
this.messagingProviderName = messagingProviderName;
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Fluent setter for messagingProviderName
** Getter for savedViewId
*******************************************************************************/
public SendMessageInput withMessagingProviderName(String messagingProviderName)
public Integer getSavedViewId()
{
this.messagingProviderName = messagingProviderName;
return (this.savedViewId);
}
/*******************************************************************************
** 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);
}

View File

@ -79,12 +79,6 @@ public class ScheduledJob extends QRecordEntity
@QField(isRequired = true)
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)
private List<ScheduledJobParameter> jobParameters;
@ -499,65 +493,4 @@ 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);
}
}

View File

@ -169,7 +169,7 @@ public class ScheduledJobsMetaDataProvider
.withRecordLabelFields("label")
.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("settings", new QIcon().withName("tune"), Tier.T2, List.of("type", "isActive", "schedulerName", "foreignKeyType", "foreignKeyValue")))
.withSection(new QFieldSection("settings", new QIcon().withName("tune"), Tier.T2, List.of("type", "isActive", "schedulerName")))
.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")));

View File

@ -32,7 +32,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
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.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -59,7 +58,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/
public class ScheduledJobTableCustomizer implements TableCustomizerInterface
{
private QBackendTransaction transaction = null;
/*******************************************************************************
**
@ -67,7 +65,6 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
transaction = insertInput.getTransaction();
validateConditionalFields(records, Collections.emptyMap());
return (records);
}
@ -80,7 +77,6 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
transaction = insertInput.getTransaction();
scheduleJobsForRecordList(records);
return (records);
}
@ -93,7 +89,6 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
@Override
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);
validateConditionalFields(records, freshOldRecordsWithAssociationsMap);
@ -174,7 +169,6 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
@Override
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
transaction = updateInput.getTransaction();
if(oldRecordList.isPresent())
{
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
@ -207,7 +201,6 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
@Override
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
transaction = deleteInput.getTransaction();
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
unscheduleJobsForRecordList(records, idsWithErrors);
return (records);
@ -269,13 +262,12 @@ public class ScheduledJobTableCustomizer implements TableCustomizerInterface
/*******************************************************************************
**
*******************************************************************************/
private List<QRecord> freshlyQueryForRecordsWithAssociations(List<QRecord> records) throws QException
private static List<QRecord> freshlyQueryForRecordsWithAssociations(List<QRecord> records) throws QException
{
List<Integer> idList = records.stream().map(r -> r.getValueInteger("id")).toList();
return new QueryAction().execute(new QueryInput(ScheduledJob.TABLE_NAME)
.withIncludeAssociations(true)
.withTransaction(transaction)
.withFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, idList))))
.getRecords();
}

View File

@ -1,46 +0,0 @@
/*
* 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;
}

View File

@ -1,123 +0,0 @@
/*
* 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));
}
}
}

View File

@ -29,27 +29,15 @@ import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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.reporting.GenerateReportAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
import com.kingsrook.qqq.backend.core.actions.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.QUserFacingException;
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.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
@ -65,10 +53,8 @@ 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.RenderedReportStatus;
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.StringUtils;
import org.apache.commons.validator.EmailValidator;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -94,29 +80,12 @@ public class RenderSavedReportExecuteStep implements BackendStep
////////////////////////////////
// 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);
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));
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
String storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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);
OutputStream outputStream = new StorageAction().createOutputStream(new StorageInput(storageTableName).withReference(storageReference));
LOG.info("Starting to render a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference));
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
@ -147,10 +116,6 @@ public class RenderSavedReportExecuteStep implements BackendStep
.withReportFormat(reportFormat)
.withReportOutputStream(outputStream));
//////////////////////////
// todo variable-values //
//////////////////////////
Map<String, Serializable> values = runBackendStepInput.getValues();
reportInput.setInputValues(values);
@ -167,41 +132,10 @@ public class RenderSavedReportExecuteStep implements BackendStep
.withValue("rowCount", reportOutput.getTotalRecordCount())
));
String downloadFileName = downloadFileBaseName + "." + reportFormat.getExtension();
runBackendStepOutput.addValue("downloadFileName", downloadFileName);
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + "." + reportFormat.getExtension());
runBackendStepOutput.addValue("storageTableName", storageTableName);
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()));
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)
{
@ -222,42 +156,6 @@ 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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -38,7 +38,6 @@ 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.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
/*******************************************************************************
@ -48,13 +47,8 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
{
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_REPORT_FORMAT = "reportFormat";
public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress";
public static final String FIELD_NAME_EMAIL_SUBJECT = "emailSubject";
@ -69,34 +63,23 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withLabel("Render Report")
.withTableName(SavedReport.TABLE_NAME)
.withIcon(new QIcon().withName("print"))
.addStep(new QBackendStepMetaData()
.withName("pre")
.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))
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))
.addStep(new QFrontendStepMetaData()
.withName("input")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
.withFormField(new QFieldMetaData(FIELD_NAME_REPORT_FORMAT, QFieldType.STRING)
.withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME)
.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)))
.withIsRequired(true)))
.addStep(new QBackendStepMetaData()
.withName("execute")
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()
.withTableName(SavedReport.TABLE_NAME)))
.withCode(new QCodeReference(RenderSavedReportExecuteStep.class)))
.addStep(new QFrontendStepMetaData()
.withName("output")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM)));

View File

@ -63,20 +63,17 @@ public class RenderSavedReportPreStep implements BackendStep
List<QRecord> records = runBackendStepInput.getRecords();
if(!CollectionUtils.nullSafeHasContents(records))
{
throw (new QUserFacingException("No report was selected or found."));
throw (new QUserFacingException("No report was selected or found to be rendered."));
}
if(records.size() > 1)
{
throw (new QUserFacingException("You may only run 1 report at a time."));
throw (new QUserFacingException("You may only render 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));
runBackendStepOutput.addValue("savedReportId", savedReport.getId());
// todo - check for inputs - set up the input screen...
}
}

View File

@ -1,105 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,76 +0,0 @@
/*
* 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);
}
}

View File

@ -26,7 +26,6 @@ import java.io.Serializable;
import java.util.List;
import java.util.Map;
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.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@ -44,10 +43,8 @@ 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.processes.QProcessMetaData;
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.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
@ -141,27 +138,13 @@ public class SchedulerUtils
RunProcessInput runProcessInput = new RunProcessInput();
runProcessInput.setProcessName(process.getName());
Serializable recordId = null;
for(Map.Entry<String, Serializable> entry : CollectionUtils.nonNullMap(processInputValues).entrySet())
{
runProcessInput.withValue(entry.getKey(), entry.getValue());
if(entry.getKey().equals("recordId"))
{
recordId = entry.getValue();
}
}
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);
RunProcessAction runProcessAction = new RunProcessAction();

View File

@ -244,6 +244,7 @@ public class JsonUtils
.registerModule(new JavaTimeModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
/* todo - some future version we may need to do inclusion/exclusion lists like this:

View File

@ -1,67 +0,0 @@
/*
* 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!"))
);
}
}

View File

@ -1,243 +0,0 @@
/*
* 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());
}
}

View File

@ -74,9 +74,6 @@ 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.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.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
@ -183,9 +180,6 @@ public class TestUtils
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 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 TEST_SQS_QUEUE = "testSQSQueue";
@ -248,9 +242,6 @@ public class TestUtils
qInstance.addQueueProvider(defineSqsProvider());
qInstance.addQueue(defineTestSqsQueue());
qInstance.addMessagingProvider(defineEmailMessagingProvider());
qInstance.addMessagingProvider(defineSESMessagingProvider());
defineWidgets(qInstance);
defineApps(qInstance);
@ -261,37 +252,6 @@ 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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -51,7 +51,7 @@ public class FilesystemStorageAction extends AbstractFilesystemAction implements
try
{
String fullPath = getFullPath(storageInput);
File file = new File(fullPath);
File file = new File(fullPath);
if(!file.getParentFile().exists())
{
if(!file.getParentFile().mkdirs())
@ -100,15 +100,4 @@ public class FilesystemStorageAction extends AbstractFilesystemAction implements
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getDownloadURL(StorageInput storageInput) throws QException
{
return ("file://" + getFullPath(storageInput));
}
}

View File

@ -26,7 +26,6 @@ import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
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.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
@ -112,53 +111,7 @@ public class S3StorageAction extends AbstractS3Action implements QStorageInterfa
}
catch(Exception 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));
throw (new QException("Exception getting s3 input stream for file", e));
}
}

View File

@ -353,8 +353,10 @@ public class SharingTest
//////////////////////////////////////////////////////////////////////////
// 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)));
assertThat(updateOutput.getRecords().get(0).getErrors()).isNotEmpty();
*/
///////////////////////////////////////////////////////////////////////
// Add that user (2) to the session - then the update should succeed //