mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Merge branch 'feature/sprint-21' into dev
This commit is contained in:
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.queues;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.sqs.AmazonSQS;
|
||||
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
|
||||
import com.amazonaws.services.sqs.model.GetQueueAttributesResult;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GetQueueSize
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetQueueSize.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getQueueSize(QQueueProviderMetaData queueProviderMetaData, QQueueMetaData queueMetaData) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// todo - handle other queue provider types, somewhere, somehow //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
SQSQueueProviderMetaData queueProvider = (SQSQueueProviderMetaData) queueProviderMetaData;
|
||||
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials(queueProvider.getAccessKey(), queueProvider.getSecretKey());
|
||||
final AmazonSQS sqs = AmazonSQSClientBuilder.standard()
|
||||
.withRegion(queueProvider.getRegion())
|
||||
.withCredentials(new AWSStaticCredentialsProvider(credentials))
|
||||
.build();
|
||||
|
||||
String queueUrl = queueProvider.getBaseURL();
|
||||
if(!queueUrl.endsWith("/"))
|
||||
{
|
||||
queueUrl += "/";
|
||||
}
|
||||
queueUrl += queueMetaData.getQueueName();
|
||||
|
||||
GetQueueAttributesResult queueAttributes = sqs.getQueueAttributes(queueUrl, List.of("ApproximateNumberOfMessages"));
|
||||
String approximateNumberOfMessages = queueAttributes.getAttributes().get("ApproximateNumberOfMessages");
|
||||
return (Integer.parseInt(approximateNumberOfMessages));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting queue size", e, logPair("queueName", queueMetaData == null ? "null" : queueMetaData.getName()));
|
||||
throw (new QException("Error getting queue size", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
@ -35,6 +36,8 @@ public abstract class AbstractWidgetValueSource
|
||||
protected String name;
|
||||
protected String type;
|
||||
|
||||
protected Map<String, Serializable> inputValues;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -116,4 +119,35 @@ public abstract class AbstractWidgetValueSource
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for inputValues
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getInputValues()
|
||||
{
|
||||
return (this.inputValues);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for inputValues
|
||||
*******************************************************************************/
|
||||
public void setInputValues(Map<String, Serializable> inputValues)
|
||||
{
|
||||
this.inputValues = inputValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for inputValues
|
||||
*******************************************************************************/
|
||||
public AbstractWidgetValueSource withInputValues(Map<String, Serializable> inputValues)
|
||||
{
|
||||
this.inputValues = inputValues;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.dashboard.nocode;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.queues.GetQueueSize;
|
||||
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.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QueueSizeWidgetValue extends AbstractWidgetValueSource
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueueSizeWidgetValue.class);
|
||||
|
||||
private String queueName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object evaluate(Map<String, Object> context, RenderWidgetInput input) throws QException
|
||||
{
|
||||
QQueueMetaData queue = QContext.getQInstance().getQueue(queueName);
|
||||
QQueueProviderMetaData queueProvider = QContext.getQInstance().getQueueProvider(queue.getProviderName());
|
||||
return (new GetQueueSize().getQueueSize(queueProvider, queue));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QueueSizeWidgetValue withName(String name)
|
||||
{
|
||||
setName(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queueName
|
||||
*******************************************************************************/
|
||||
public String getQueueName()
|
||||
{
|
||||
return (this.queueName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queueName
|
||||
*******************************************************************************/
|
||||
public void setQueueName(String queueName)
|
||||
{
|
||||
this.queueName = queueName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queueName
|
||||
*******************************************************************************/
|
||||
public QueueSizeWidgetValue withQueueName(String queueName)
|
||||
{
|
||||
this.queueName = queueName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.dashboard.nocode;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class WidgetAdHocValue extends AbstractWidgetValueSource
|
||||
{
|
||||
private QCodeReference codeReference;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public WidgetAdHocValue()
|
||||
{
|
||||
setType(getClass().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object evaluate(Map<String, Object> context, RenderWidgetInput input) throws QException
|
||||
{
|
||||
if(inputValues != null)
|
||||
{
|
||||
context.putAll(inputValues);
|
||||
}
|
||||
|
||||
Function<Object, Object> function = QCodeLoader.getFunction(codeReference);
|
||||
Object result = function.apply(context);
|
||||
return (result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public WidgetAdHocValue withName(String name)
|
||||
{
|
||||
setName(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for codeReference
|
||||
*******************************************************************************/
|
||||
public QCodeReference getCodeReference()
|
||||
{
|
||||
return (this.codeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for codeReference
|
||||
*******************************************************************************/
|
||||
public void setCodeReference(QCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for codeReference
|
||||
*******************************************************************************/
|
||||
public WidgetAdHocValue withCodeReference(QCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for inputValues
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public WidgetAdHocValue withInputValues(Map<String, Serializable> inputValues)
|
||||
{
|
||||
this.inputValues = inputValues;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -74,14 +74,14 @@ public class WidgetCalculation extends AbstractWidgetValueSource
|
||||
{
|
||||
Instant now = Instant.now();
|
||||
Instant then = ValueUtils.getValueAsInstant(context.get(valueNames.get(0)));
|
||||
return (then.until(now, ChronoUnit.MINUTES));
|
||||
return (then == null ? null : then.until(now, ChronoUnit.MINUTES));
|
||||
}),
|
||||
|
||||
AGE_SECONDS((List<String> valueNames, Map<String, Object> context) ->
|
||||
{
|
||||
Instant now = Instant.now();
|
||||
Instant then = ValueUtils.getValueAsInstant(context.get(valueNames.get(0)));
|
||||
return (then.until(now, ChronoUnit.SECONDS));
|
||||
return (then == null ? null : then.until(now, ChronoUnit.SECONDS));
|
||||
}),
|
||||
|
||||
PERCENT_CHANGE((List<String> valueNames, Map<String, Object> context) ->
|
||||
@ -92,8 +92,8 @@ public class WidgetCalculation extends AbstractWidgetValueSource
|
||||
///////////////////////////////////////////////
|
||||
// 100 * ( (current - previous) / previous ) //
|
||||
///////////////////////////////////////////////
|
||||
BigDecimal difference = current.subtract(previous);
|
||||
if(BigDecimal.ZERO.equals(previous))
|
||||
BigDecimal difference = current == null ? null : current.subtract(previous);
|
||||
if(BigDecimal.ZERO.equals(previous) || difference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
@ -313,6 +313,42 @@ public class StringUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Given a "formatString" containing any number of {singular,plural} style "tokens",
|
||||
** replace the "tokens" with the "singular" options if the 'size' parameter is 1
|
||||
** or the "plural" options if not-1 (e.g., 0 or 2+)
|
||||
**
|
||||
** e.g.: StringUtils.pluralFormat(n, "Apple{,s} {was,were} eaten")) // seems easier.
|
||||
** e.g.: StringUtils.pluralFormat(n, "Apple{ was,s were} eaten")) // also works...
|
||||
*******************************************************************************/
|
||||
public static String pluralFormat(Integer size, String formatString)
|
||||
{
|
||||
int lastIndex = 0;
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
Pattern pattern = Pattern.compile("\\{.*?,.*?}");
|
||||
Matcher matcher = pattern.matcher(formatString);
|
||||
while(matcher.find())
|
||||
{
|
||||
String group = matcher.group();
|
||||
String groupBody = group.substring(1, group.length() - 1);
|
||||
String[] groupParts = groupBody.split(",", 2);
|
||||
String replacement = (size == 1) ? groupParts[0] : groupParts[1];
|
||||
output.append(formatString, lastIndex, matcher.start()).append(replacement);
|
||||
|
||||
lastIndex = matcher.end();
|
||||
}
|
||||
|
||||
if(lastIndex < formatString.length())
|
||||
{
|
||||
output.append(formatString, lastIndex, formatString.length());
|
||||
}
|
||||
|
||||
return (output.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Switch between strings based on if the size of the parameter collection. If
|
||||
** it is 1 (the singular) or not-1 (0 or 2+, the plural). Get back "" or "s"
|
||||
|
@ -285,4 +285,23 @@ class StringUtilsTest extends BaseTest
|
||||
assertEquals("Abc", StringUtils.ucFirst("abc"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPluralFormat()
|
||||
{
|
||||
assertEquals("Apple", StringUtils.pluralFormat(1, "Apple{,s}"));
|
||||
assertEquals("Apples", StringUtils.pluralFormat(0, "Apple{,s}"));
|
||||
assertEquals("Apples", StringUtils.pluralFormat(2, "Apple{,s}"));
|
||||
|
||||
assertEquals("Apple and Orange", StringUtils.pluralFormat(1, "Apple{,s} and Orange{,s}"));
|
||||
assertEquals("Apples and Oranges", StringUtils.pluralFormat(2, "Apple{,s} and Orange{,s}"));
|
||||
|
||||
assertEquals("Apple was eaten", StringUtils.pluralFormat(1, "Apple{,s} {was,were} eaten"));
|
||||
assertEquals("Apples were eaten", StringUtils.pluralFormat(2, "Apple{,s} {was,were} eaten"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@ -49,6 +50,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
@ -59,6 +61,8 @@ import org.apache.commons.lang.NotImplementedException;
|
||||
*******************************************************************************/
|
||||
public class QueryManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryManager.class);
|
||||
|
||||
public static final int DEFAULT_PAGE_SIZE = 2000;
|
||||
public static int PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
||||
|
||||
@ -1409,13 +1413,44 @@ public class QueryManager
|
||||
*******************************************************************************/
|
||||
public static Instant getInstant(ResultSet resultSet, int column) throws SQLException
|
||||
{
|
||||
Timestamp value = resultSet.getTimestamp(column);
|
||||
if(resultSet.wasNull())
|
||||
try
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this will be a zone-less date-time string, in the database server's configured timezone //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
String string = resultSet.getString(column);
|
||||
if(resultSet.wasNull())
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (value.toInstant());
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make an Instant (which means UTC) from that zone-less date-time string. //
|
||||
// if the database server was giving back non-utc times, we'd need a different ZoneId here? //
|
||||
// e.g., as configured via ... a system property or database metadata setting //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(string.replace(' ', 'T'));
|
||||
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("UTC"));
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
return (instant);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error getting an instant value from a database result - proceeding with potentially wrong-timezone implementation...", e);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if for some reason the parsing and stuff above fails, well, this will give us back "some" date, maybe //
|
||||
// this was our old logic, which probably had timezones wrong if server wasn't in UTC //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Timestamp value = resultSet.getTimestamp(column);
|
||||
if(resultSet.wasNull())
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
Instant instant = value.toInstant();
|
||||
return (instant);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user