diff --git a/das-jira-connector/pom.xml b/das-jira-connector/pom.xml
index 9be3fe2..cb77028 100644
--- a/das-jira-connector/pom.xml
+++ b/das-jira-connector/pom.xml
@@ -117,6 +117,12 @@
jackson-datatype-joda
${jackson.version}
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.11.1
+ test
+
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraIssueTransformationTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraIssueTransformationTable.java
index fba3d34..1f8488f 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraIssueTransformationTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraIssueTransformationTable.java
@@ -3,13 +3,23 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.rawlabs.das.jira.DASJiraUnexpectedError;
import com.rawlabs.protocol.das.v1.tables.Row;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.*;
@SuppressWarnings("unchecked")
public abstract class DASJiraIssueTransformationTable extends DASJiraTable {
+
+ private final ZoneId localZoneId;
+ private final ZoneId remoteZoneId;
+ DateTimeFormatter offsetTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+
protected DASJiraIssueTransformationTable(
- Map options, String table, String description) {
+ Map options, ZoneId localZoneId, ZoneId remoteZoneId, String table, String description) {
super(options, table, description);
+ this.localZoneId = localZoneId;
+ this.remoteZoneId = remoteZoneId;
}
protected void processFields(
@@ -51,11 +61,12 @@ protected void processFields(
assignee.map(a -> a.get("displayName")).orElse(null),
columns);
- addToRow(
- "created",
- rowBuilder,
- maybeFields.map(f -> f.get(names.get("Created"))).orElse(null),
- columns);
+ String created =
+ maybeFields
+ .map(f -> f.get(names.get("Created")))
+ .map(c -> toLocal(c.toString()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
+ .orElse(null);
+ addToRow("created", rowBuilder, created, columns);
var creator =
maybeFields.map(f -> f.get(names.get("Creator"))).map(p -> (Map) p);
@@ -86,11 +97,13 @@ protected void processFields(
throw new DASJiraUnexpectedError(e);
}
- addToRow(
- "due_date",
- rowBuilder,
- maybeFields.map(f -> f.get(names.get("Due date"))).orElse(null),
- columns);
+ String due_date =
+ maybeFields
+ .flatMap(f -> Optional.ofNullable(f.get(names.get("Due date"))))
+ .map(Object::toString)
+ .orElse(null);
+
+ addToRow("due_date", rowBuilder, due_date, columns);
var priority =
maybeFields.map(f -> f.get(names.get("Priority"))).map(p -> (Map) p);
@@ -105,12 +118,20 @@ protected void processFields(
rowBuilder,
reporter.map(r -> r.get("accountId")).orElse(null),
columns);
+
addToRow(
"reporter_display_name",
rowBuilder,
reporter.map(r -> r.get("displayName")).orElse(null),
columns);
+ String resolved =
+ maybeFields
+ .flatMap(f -> Optional.ofNullable(f.get(names.get("Resolved"))))
+ .map(c -> toLocal(c.toString()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
+ .orElse(null);
+ addToRow("resolution_date", rowBuilder, resolved, columns);
+
addToRow(
"summary",
rowBuilder,
@@ -128,7 +149,10 @@ protected void processFields(
addToRow(
"updated",
rowBuilder,
- maybeFields.map(f -> f.get(names.get("Updated"))).orElse(null),
+ maybeFields
+ .map(f -> f.get(names.get("Updated")))
+ .map(c -> toLocal(c.toString()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
+ .orElse(null),
columns);
var componentIds =
@@ -176,4 +200,10 @@ protected void processFields(
throw new DASJiraUnexpectedError(e);
}
}
+
+ private OffsetDateTime toLocal(String remoteTime) {
+ return OffsetDateTime.parse(remoteTime, offsetTimeFormatter)
+ .atZoneSameInstant(localZoneId)
+ .toOffsetDateTime();
+ }
}
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilder.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilder.java
index 6315cb5..a1b107e 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilder.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilder.java
@@ -5,19 +5,29 @@
import com.rawlabs.protocol.das.v1.query.Operator;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.types.Value;
+import com.rawlabs.protocol.das.v1.types.ValueTimestamp;
+
import java.time.OffsetDateTime;
import java.time.OffsetTime;
+
+import java.time.*;
import java.time.format.DateTimeFormatter;
-import java.util.List;
-import java.util.Map;
-import java.util.StringJoiner;
+import java.util.*;
public class DASJiraJqlQueryBuilder {
+ private final ZoneId localZoneId;
+ private final ZoneId remoteZoneId;
+
+ public DASJiraJqlQueryBuilder(ZoneId localZoneId, ZoneId remoteZoneId) {
+ this.localZoneId = localZoneId;
+ this.remoteZoneId = remoteZoneId;
+ }
+
private static final ExtractValueFactory extractValueFactory = new DefaultExtractValueFactory();
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
- static String mapOperator(Operator operator) {
+ String mapOperator(Operator operator) {
return switch (operator) {
case Operator.EQUALS -> "=";
case Operator.NOT_EQUALS -> "!=";
@@ -29,7 +39,7 @@ static String mapOperator(Operator operator) {
};
}
- static String mapValue(Value value) {
+ String mapValue(Value value) {
String s =
switch (value) {
case Value v when v.hasString() -> v.getString().getV();
@@ -37,46 +47,458 @@ static String mapValue(Value value) {
OffsetTime time = (OffsetTime) extractValueFactory.extractValue(value);
yield time.format(formatter);
}
- case Value v when (v.hasTimestamp() || v.hasDate()) -> {
+ case Value v when v.hasDate() -> {
OffsetDateTime time = (OffsetDateTime) extractValueFactory.extractValue(value);
- yield time.format(formatter);
+ yield time.toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
+ }
+ case Value v when v.hasTimestamp() -> {
+ // Without time zone, it's a local time, so we need to set its timezone to the
+ // local timezone and then convert it to the remote timezone.
+ ValueTimestamp rawTimestamp = v.getTimestamp();
+ LocalDateTime time =
+ LocalDateTime.of(
+ rawTimestamp.getYear(),
+ rawTimestamp.getMonth(),
+ rawTimestamp.getDay(),
+ rawTimestamp.getHour(),
+ rawTimestamp.getMinute(),
+ rawTimestamp.getSecond(),
+ rawTimestamp.getNano());
+ ZonedDateTime dTime = time.atZone(localZoneId).withZoneSameInstant(remoteZoneId);
+ yield dTime.format(formatter);
}
default -> throw new IllegalArgumentException("Unexpected value: " + value);
};
- return "\"" + s + "\"";
+ // If the string is all digits, it's a number and we don't need to quote it. This permits
+ // to filter on 'id' without quotes, which is the required syntax. If it's not all digits, we
+ // quote it, because it doesn't hurt, and it protects potential keywords.
+ boolean allDigits = s.chars().allMatch(Character::isDigit);
+ if (allDigits) {
+ return s;
+ } else {
+ return "\"" + s.replace("\"", "\"\"") + "\"";
+ }
}
- // The exposed column name needs to be mapped to the corresponding inner JQL key.
- // JIRA represents several fields as JSON objects, and we often expose multiple
- // nested fields from these objects as separate columns in the table output.
- // These columns are named using the pattern: original key + "_" + nested key.
- // From this naming convention, the JQL key can typically be inferred by taking
- // the first part of the column name.
- private static String getIssueJqlKey(String columnName) {
- // However, there are exceptions, such as 'status_category', which should not
- // be mapped to the JQL 'status' field, but to `statusCategory`. Exceptions are
- // found in jqlKeyMap.
- if (jqlKeyMap.containsKey(columnName)) {
- return jqlKeyMap.get(columnName);
+ // Tries to convert a list qual to a JQL string using IN or NOT IN.
+ private Optional maybeInCheck(Qual qual) {
+ Operator rawOperator;
+ List rawValues;
+ if (qual.hasIsAnyQual()) {
+ rawValues = qual.getIsAnyQual().getValuesList();
+ rawOperator = qual.getIsAnyQual().getOperator();
+ } else if (qual.hasIsAllQual()) {
+ rawValues = qual.getIsAllQual().getValuesList();
+ rawOperator = qual.getIsAllQual().getOperator();
+ } else {
+ return Optional.empty();
+ }
+ // If some values are null, we can't support this
+ if (rawValues.stream().anyMatch(Value::hasNull)) {
+ return Optional.empty();
+ }
+ List values = new ArrayList<>();
+ for (Value rawValue : rawValues) {
+ values.add(mapValue(rawValue));
+ }
+ if (qual.hasIsAnyQual() && rawOperator == Operator.EQUALS) {
+ // col EQUAL to ANY value IN (1,2,3) ==> IN
+ StringJoiner joiner = new StringJoiner(", ", "IN (", ")");
+ values.forEach(joiner::add);
+ return Optional.of(joiner.toString());
+ } else {
+ if (qual.hasIsAllQual() && rawOperator == Operator.NOT_EQUALS) {
+ // col NOT EQUAL to ALL values IN (1,2,3) ==> NOT IN
+ StringJoiner joiner = new StringJoiner(", ", "NOT IN (", ")");
+ values.forEach(joiner::add);
+ return Optional.of(joiner.toString());
+ } else {
+ // We don't support other cases (ANY > 1 value, ALL > 1 value, etc.)
+ return Optional.empty();
+ }
}
- return columnName.split("_")[0].toLowerCase();
}
- private static final Map jqlKeyMap =
- Map.of("status_category", "statusCategory", "epic_key", "parentEpic");
+ // Tries to convert an operator to a JQL string using IS NULL or IS NOT NULL. Basically only
+ // equals and not equals are supported.
+ private Optional maybeNullCheck(Operator rawOperator) {
+ if (rawOperator == Operator.EQUALS) {
+ return Optional.of("IS NULL");
+ } else if (rawOperator == Operator.NOT_EQUALS) {
+ return Optional.of("IS NOT NULL");
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ // Tries to convert a list of quals to a list of JQL optional strings. If the qual is not
+ // supported, the optional is empty. Returning a list of optionals allows us to know if all quals
+ // are supported or not, which impacts whether or not one pushes LIMIT after.
+ public List> mkJql(List quals) {
+ List> jqls = new ArrayList<>();
+ for (Qual qual : quals) {
+ String rawColumn = qual.getName();
+ if (rawColumn.equals("id") || rawColumn.equals("key") || rawColumn.equals("title")) {
+ if (qual.hasSimpleQual()) {
+ Value rawValue = qual.getSimpleQual().getValue();
+ // All simple comparisons are supported but not IS NULL OR IS NOT NULL
+ if (rawValue.hasNull()) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "issueKey" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "issueKey " + s));
+ }
+ } else if (rawColumn.equals("self")) {
+ // Not supported
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("project_key")
+ || rawColumn.equals("project_id")
+ || rawColumn.equals("project_name")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "project " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "project" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "project " + s));
+ }
+ } else if (rawColumn.equals("status")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "status " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "status" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "status " + s));
+ }
+ } else if (rawColumn.equals("status_category")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "statusCategory " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "statusCategory" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "statusCategory " + s));
+ }
+ } else if (rawColumn.equals("epic_key")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // We have to filter on the parent field.
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "parent " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "parent" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "parent " + s));
+ }
+ } else if (rawColumn.equals("sprint_ids") || rawColumn.equals("sprint_names")) {
+ // Not supported because the column is a list of values, and no predicate applies to that.
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("assignee_account_id")
+ || rawColumn.equals("assignee_email_address")
+ || rawColumn.equals("assignee_display_name")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "assignee " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "assignee" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "assignee " + s));
+ }
+ } else if (rawColumn.equals("creator_account_id")
+ || rawColumn.equals("creator_email_address")
+ || rawColumn.equals("creator_display_name")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "creator " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "creator" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "creator " + s));
+ }
+ } else if (rawColumn.equals("created")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // All are supported. IS NULL and IS NOT NULL are supported.
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "created " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "created" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "created " + s));
+ }
+ } else if (rawColumn.equals("due_date")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // All are supported. IS NULL and IS NOT NULL are supported.
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "due " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "due" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "due " + s));
+ }
+ } else if (rawColumn.equals("description")) {
+ // JSONB, not supported
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("type")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ // IN is supported
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "type " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "type" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "type " + s));
+ }
+ } else if (rawColumn.equals("labels")) {
+ // List, not supported
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("priority")) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others because they aren't regular string comparisons.
+ // IS NULL and IS NOT NULL are supported.
+ // IN is supported
+ if (qual.hasSimpleQual()) {
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "priority " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "priority" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "priority " + s));
+ }
+ } else if (rawColumn.equals("reporter_account_id")
+ || rawColumn.equals("reporter_email_address")
+ || rawColumn.equals("reporter_display_name")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // != and == are supported, not others. IS NULL and IS NOT NULL are supported.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "reporter " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "reporter" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "reporter " + s));
+ }
+ } else if (rawColumn.equals("summary")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // Only IS NULL and IS NOT NULL are supported. But we support = and != with ~ and !~
+ // with Postgres applying the operator eventually.
+ if (rawOperator != Operator.EQUALS && rawOperator != Operator.NOT_EQUALS) {
+ jqls.add(Optional.empty());
+ continue;
+ }
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "summary " + p));
+ } else if (rawValue.hasString()) {
+ String value = rawValue.getString().getV();
+ String escaped = value.replace("\"", "\\\"");
+ if (rawOperator == Operator.EQUALS) {
+ String jql = "summary ~ \"" + escaped + "\"";
+ jqls.add(Optional.of(jql));
+ } else {
+ String jql = "summary !~ \"" + escaped + "\"";
+ jqls.add(Optional.of(jql));
+ }
+ }
+ } else {
+ jqls.add(Optional.empty());
+ }
+ } else if (rawColumn.equals("updated")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // All are supported. IS NULL and IS NOT NULL are supported.
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "updated " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "updated" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "updated " + s));
+ }
+ } else if (rawColumn.equals("resolution_date")) {
+ if (qual.hasSimpleQual()) {
+ Operator rawOperator = qual.getSimpleQual().getOperator();
+ // All are supported. IS NULL and IS NOT NULL are supported.
+ Value rawValue = qual.getSimpleQual().getValue();
+ if (rawValue.hasNull()) {
+ jqls.add(maybeNullCheck(rawOperator).map(p -> "resolved " + p));
+ continue;
+ }
+ String operator = mapOperator(rawOperator);
+ String value = mapValue(rawValue);
+ String jql = "resolved" + " " + operator + " " + value;
+ jqls.add(Optional.of(jql));
+ } else {
+ // IN and NOT IN are supported
+ Optional maybeIn = maybeInCheck(qual);
+ jqls.add(maybeIn.map(s -> "resolved " + s));
+ }
+ } else if (rawColumn.equals("components")) {
+ // Not supported (it's a list)
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("fields")) {
+ // Not supported (JSONB)
+ jqls.add(Optional.empty());
+ } else if (rawColumn.equals("tags")) {
+ // Not supported (JSONB)
+ jqls.add(Optional.empty());
+ } else {
+ jqls.add(Optional.empty());
+ }
+ }
+ return jqls;
+ }
- public static String buildJqlQuery(List quals) {
+ public String buildJqlQuery(List quals) {
StringBuilder jqlQuery = new StringBuilder();
StringJoiner joiner = new StringJoiner(" AND ");
- quals.stream()
- .filter(Qual::hasSimpleQual)
- .forEach(
- q -> {
- String column = getIssueJqlKey(q.getName());
- String operator = mapOperator(q.getSimpleQual().getOperator());
- String value = mapValue(q.getSimpleQual().getValue());
- joiner.add(column + " " + operator + " " + value);
- });
+ List> jqls = mkJql(quals);
+ for (Optional jql : jqls) {
+ jql.ifPresent(joiner::add);
+ }
jqlQuery.append(joiner);
return jqlQuery.toString();
}
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTable.java
index 1a98c05..5ffff92 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTable.java
@@ -56,17 +56,19 @@ public TableDefinition getTableDefinition() {
protected void addToRow(
String columnName, Row.Builder rowBuilder, Object value, List columns) {
if (hasProjection(columns, columnName)) {
+ ColumnDefinition definition = columnDefinitions.get(columnName);
rowBuilder.addColumns(
Column.newBuilder()
.setName(columnName)
.setData(
valueFactory.createValue(
- new ValueTypeTuple(value, columnDefinitions.get(columnName).getType()))));
+ new ValueTypeTuple(value, definition.getType()))));
}
}
private boolean hasProjection(List columns, String columnName) {
- return columns == null || columns.isEmpty() || columns.contains(columnName);
+ return columnDefinitions.containsKey(columnName)
+ && (columns == null || columns.isEmpty() || columns.contains(columnName));
}
protected abstract LinkedHashMap buildColumnDefinitions();
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTableManager.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTableManager.java
index a43bbca..e1b6c5d 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTableManager.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/DASJiraTableManager.java
@@ -1,5 +1,6 @@
package com.rawlabs.das.jira.tables;
+import com.rawlabs.das.jira.rest.platform.ApiException;
import com.rawlabs.das.jira.rest.platform.api.*;
import com.rawlabs.das.jira.rest.software.api.BoardApi;
import com.rawlabs.das.jira.rest.software.api.EpicApi;
@@ -7,7 +8,9 @@
import com.rawlabs.das.jira.tables.definitions.*;
import com.rawlabs.das.jira.tables.definitions.DASJiraAdvancedSettingsTable;
import com.rawlabs.das.jira.tables.definitions.DASJiraBoardTable;
+import com.rawlabs.das.sdk.DASSdkInvalidArgumentException;
import com.rawlabs.protocol.das.v1.tables.TableDefinition;
+import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -20,10 +23,27 @@ public DASJiraTableManager(
Map options,
com.rawlabs.das.jira.rest.platform.ApiClient apiClientPlatform,
com.rawlabs.das.jira.rest.software.ApiClient apiClientSoftware) {
+ MyselfApi myselfApi = new MyselfApi(apiClientPlatform);
+ final ZoneId jiraZoneId;
+ final ZoneId localZoneId;
+ try {
+ String jiraTimeZone = myselfApi.getCurrentUser("").getTimeZone();
+ if (jiraTimeZone == null) {
+ throw new DASSdkInvalidArgumentException("Couldn't determine JIRA timezone");
+ }
+ jiraZoneId = ZoneId.of(jiraTimeZone);
+ } catch (ApiException e) {
+ throw new DASSdkInvalidArgumentException("Error getting user timezone", e);
+ }
+ if (options.get("timezone") == null) {
+ localZoneId = ZoneId.of("UTC");
+ } else {
+ localZoneId = ZoneId.of(options.get("timezone"));
+ }
tables =
List.of(
new DASJiraAdvancedSettingsTable(options, new JiraSettingsApi(apiClientPlatform)),
- new DASJiraBacklogIssueTable(options, new BoardApi(apiClientSoftware)),
+ new DASJiraBacklogIssueTable(options, localZoneId, jiraZoneId, new BoardApi(apiClientSoftware)),
new DASJiraBoardTable(options, new BoardApi(apiClientSoftware)),
new DASJiraComponentTable(
options,
@@ -35,14 +55,22 @@ public DASJiraTableManager(
new DASJiraGroupTable(options, new GroupsApi(apiClientPlatform)),
new DASJiraIssueCommentTable(
options,
+ localZoneId,
+ jiraZoneId,
new IssueCommentsApi(apiClientPlatform),
new IssueSearchApi(apiClientPlatform),
new IssuesApi(apiClientPlatform)),
new DASJiraIssueTable(
- options, new IssueSearchApi(apiClientPlatform), new IssuesApi(apiClientPlatform)),
+ options,
+ localZoneId,
+ jiraZoneId,
+ new IssueSearchApi(apiClientPlatform),
+ new IssuesApi(apiClientPlatform)),
new DASJiraIssueTypeTable(options, new IssueTypesApi(apiClientPlatform)),
new DASJiraIssueWorklogTable(
options,
+ localZoneId,
+ jiraZoneId,
new IssueWorklogsApi(apiClientPlatform),
new IssueSearchApi(apiClientPlatform),
new IssuesApi(apiClientPlatform)),
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraBacklogIssueTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraBacklogIssueTable.java
index fc55ad1..15b2555 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraBacklogIssueTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraBacklogIssueTable.java
@@ -22,6 +22,7 @@
import com.rawlabs.protocol.das.v1.tables.Row;
import com.rawlabs.protocol.das.v1.types.Value;
import com.rawlabs.protocol.das.v1.types.ValueLong;
+import java.time.ZoneId;
import java.util.*;
import org.jetbrains.annotations.Nullable;
@@ -33,9 +34,11 @@ public class DASJiraBacklogIssueTable extends DASJiraIssueTransformationTable {
private final BoardApi boardApi;
- public DASJiraBacklogIssueTable(Map options, BoardApi boardApi) {
+ public DASJiraBacklogIssueTable(Map options, ZoneId localZoneId, ZoneId jiraZoneId, BoardApi boardApi) {
super(
options,
+ localZoneId,
+ jiraZoneId,
TABLE_NAME,
"The backlog contains incomplete issues that are not assigned to any future or active sprint.");
this.boardApi = boardApi;
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueCommentTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueCommentTable.java
index dc40a51..92aeaa7 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueCommentTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueCommentTable.java
@@ -20,6 +20,7 @@
import com.rawlabs.protocol.das.v1.tables.Row;
import com.rawlabs.protocol.das.v1.types.Value;
import java.io.IOException;
+import java.time.ZoneId;
import java.util.*;
import org.jetbrains.annotations.Nullable;
@@ -33,12 +34,14 @@ public class DASJiraIssueCommentTable extends DASJiraTable {
public DASJiraIssueCommentTable(
Map options,
+ ZoneId localZoneId,
+ ZoneId jiraZoneId,
IssueCommentsApi issueCommentsApi,
IssueSearchApi issueSearchApi,
IssuesApi issuesApi) {
super(options, TABLE_NAME, "Comments that provided in issue.");
this.issueCommentsApi = issueCommentsApi;
- this.parentTable = new DASJiraIssueTable(options, issueSearchApi, issuesApi);
+ this.parentTable = new DASJiraIssueTable(options, localZoneId, jiraZoneId, issueSearchApi, issuesApi);
}
public List getTableSortOrders(List sortKeys) {
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueTable.java
index 9ca8ebd..ecc1756 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueTable.java
@@ -19,21 +19,26 @@
import com.rawlabs.protocol.das.v1.tables.ColumnDefinition;
import com.rawlabs.protocol.das.v1.tables.Row;
import com.rawlabs.protocol.das.v1.types.Value;
-import java.util.*;
import org.jetbrains.annotations.Nullable;
+import java.time.ZoneId;
+import java.util.*;
public class DASJiraIssueTable extends DASJiraIssueTransformationTable {
public static final String TABLE_NAME = "jira_issue";
private final IssueSearchApi issueSearchApi;
private final IssuesApi issuesApi;
+ private final ZoneId localZoneId;
+ private final ZoneId jiraZoneId;
public DASJiraIssueTable(
- Map options, IssueSearchApi issueSearchApi, IssuesApi issueApi) {
+ Map options, ZoneId localZoneId, ZoneId jiraZoneId, IssueSearchApi issueSearchApi, IssuesApi issueApi) {
super(
- options, TABLE_NAME, "Issues help manage code, estimate workload, and keep track of team.");
+ options, localZoneId, jiraZoneId, TABLE_NAME, "Issues help manage code, estimate workload, and keep track of team.");
this.issueSearchApi = issueSearchApi;
this.issuesApi = issueApi;
+ this.localZoneId = localZoneId;
+ this.jiraZoneId = jiraZoneId;
}
public String uniqueColumn() {
@@ -69,7 +74,8 @@ public void delete(Value rowId) {
@Override
public List explain(
List quals, List columns, List sortKeys, @Nullable Long limit) {
- return List.of(DASJiraJqlQueryBuilder.buildJqlQuery(quals));
+ DASJiraJqlQueryBuilder queryBuilder = new DASJiraJqlQueryBuilder(localZoneId, jiraZoneId);
+ return List.of(queryBuilder.buildJqlQuery(quals));
}
public DASExecuteResult execute(
@@ -84,7 +90,7 @@ public Row next() {
@Override
public DASJiraPage fetchPage(long offset) {
try {
- String jql = DASJiraJqlQueryBuilder.buildJqlQuery(quals);
+ String jql = new DASJiraJqlQueryBuilder(localZoneId, jiraZoneId).buildJqlQuery(quals);
var result =
issueSearchApi.searchForIssuesUsingJql(
jql,
@@ -254,7 +260,7 @@ protected LinkedHashMap buildColumnDefinitions() {
createColumn(
"due_date",
"Time by which the issue is expected to be completed",
- createTimestampType()));
+ createDateType()));
columns.put(
"description", createColumn("description", "Description of the issue", createAnyType()));
columns.put("type", createColumn("type", "The name of the issue type", createStringType()));
diff --git a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueWorklogTable.java b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueWorklogTable.java
index fc23246..a14dae6 100644
--- a/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueWorklogTable.java
+++ b/das-jira-connector/src/main/java/com/rawlabs/das/jira/tables/definitions/DASJiraIssueWorklogTable.java
@@ -26,6 +26,7 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.time.ZoneId;
import java.util.*;
import org.jetbrains.annotations.Nullable;
@@ -39,6 +40,8 @@ public class DASJiraIssueWorklogTable extends DASJiraTable {
public DASJiraIssueWorklogTable(
Map options,
+ ZoneId localZoneId,
+ ZoneId jiraZoneId,
IssueWorklogsApi issueWorklogsApi,
IssueSearchApi issueSearchApi,
IssuesApi issuesApi) {
@@ -47,7 +50,7 @@ public DASJiraIssueWorklogTable(
TABLE_NAME,
"Jira worklog is a feature within the Jira software that allows users to record the amount of time they have spent working on various tasks or issues.");
this.issueWorklogsApi = issueWorklogsApi;
- parentTable = new DASJiraIssueTable(options, issueSearchApi, issuesApi);
+ parentTable = new DASJiraIssueTable(options, localZoneId, jiraZoneId, issueSearchApi, issuesApi);
}
public String uniqueColumn() {
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilderTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilderTest.java
index 471d954..fde4353 100644
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilderTest.java
+++ b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraJqlQueryBuilderTest.java
@@ -1,77 +1,1243 @@
package com.rawlabs.das.jira.tables;
-import static com.rawlabs.das.jira.utils.factory.qual.QualFactory.*;
-import static com.rawlabs.das.jira.utils.factory.type.TypeFactory.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.rawlabs.das.jira.utils.factory.value.DefaultValueFactory;
-import com.rawlabs.das.jira.utils.factory.value.ValueFactory;
-import com.rawlabs.das.jira.utils.factory.value.ValueTypeTuple;
-import com.rawlabs.protocol.das.v1.query.Operator;
-import java.util.List;
+import com.rawlabs.protocol.das.v1.query.*;
+import com.rawlabs.protocol.das.v1.types.*;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-@DisplayName("Jira JQL Query Builder")
-public class DASJiraJqlQueryBuilderTest {
-
- private final ValueFactory valueFactory = new DefaultValueFactory();
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Optional;
- @Test
- @DisplayName("Should map only string or temporal values")
- public void shouldMapValues() {
+/** Comprehensive coverage for Qual across all columns that DASJiraJqlQueryBuilder supports. */
+@DisplayName("DASJiraJqlQueryBuilder - All SimpleQual Tests")
+public class DASJiraJqlQueryBuilderTest {
- var string =
- DASJiraJqlQueryBuilder.mapValue(
- valueFactory.createValue(new ValueTypeTuple("DAS", createStringType())));
+ private final DASJiraJqlQueryBuilder jqlBuilder =
+ new DASJiraJqlQueryBuilder(ZoneId.of("UTC"), ZoneId.of("UTC"));
- var timestamp =
- DASJiraJqlQueryBuilder.mapValue(
- valueFactory.createValue(
- new ValueTypeTuple("2021-01-01T00:00:00Z", createTimestampType())));
+ // ------------------------------------------------------------------------
+ // Helper methods
+ // ------------------------------------------------------------------------
- assertEquals("\"DAS\"", string);
- assertEquals("\"2021-01-01 00:00\"", timestamp);
- assertThrows(
- IllegalArgumentException.class,
- () ->
- DASJiraJqlQueryBuilder.mapValue(
- valueFactory.createValue(new ValueTypeTuple(1, createIntType()))));
+ private Value stringValue(String s) {
+ return Value.newBuilder().setString(ValueString.newBuilder().setV(s)).build();
}
@Test
@DisplayName("Should map operators to JQL")
public void shouldMapOperators() {
-
+ var queryBuilder = new DASJiraJqlQueryBuilder(null, null);
var geq =
- DASJiraJqlQueryBuilder.mapOperator(Operator.GREATER_THAN_OR_EQUAL);
+ queryBuilder.mapOperator(Operator.GREATER_THAN_OR_EQUAL);
- var eq = DASJiraJqlQueryBuilder.mapOperator(Operator.EQUALS);
+ var eq = queryBuilder.mapOperator(Operator.EQUALS);
assertEquals(">=", geq);
assertEquals("=", eq);
+}
+
+ private Value nullValue() {
+ return Value.newBuilder().setNull(ValueNull.getDefaultInstance()).build();
+ }
+
+ private Value timestampValue(int year, int month, int day, int hour, int min, int sec, int nano) {
+ return Value.newBuilder()
+ .setTimestamp(
+ ValueTimestamp.newBuilder()
+ .setYear(year)
+ .setMonth(month)
+ .setDay(day)
+ .setHour(hour)
+ .setMinute(min)
+ .setSecond(sec)
+ .setNano(nano))
+ .build();
+ }
+
+ private Value dateValue(int year, int month, int day) {
+ return Value.newBuilder()
+ .setDate(ValueDate.newBuilder().setYear(year).setMonth(month).setDay(day))
+ .build();
+ }
+
+ private Qual simpleQual(String column, Operator op, Value val) {
+ return Qual.newBuilder()
+ .setName(column)
+ .setSimpleQual(SimpleQual.newBuilder().setOperator(op).setValue(val).build())
+ .build();
+ }
+
+ /**
+ * Creates a ListQual for (column operator [values]) with isAny controlling IN or NOT IN usage: -
+ * eq + isAny=true => IN(...) - ne + isAny=false => NOT IN(...)
+ */
+ private Qual listQual(String column, Operator op, boolean isAny, List values) {
+ Qual.Builder builder = Qual.newBuilder().setName(column);
+ if (isAny) {
+ builder.setIsAnyQual(IsAnyQual.newBuilder().setOperator(op).addAllValues(values));
+ } else {
+ builder.setIsAllQual(IsAllQual.newBuilder().setOperator(op).addAllValues(values));
+ }
+ return builder.build();
+ }
+
+ // --------------------------------------------------------------------------
+ // Columns: "id", "key", "title" => all turn into JQL field "issueKey"
+ // - All comparisons are supported: =, !=, <, <=, >, >=
+ // - Null is not supported => mkJql returns Optional.empty()
+ // - All values are quoted except for numeric values
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName(
+ "Columns: id/key/title => issueKey (All comparisons supported, Null not supported, IN supported)")
+ class IssueKeyTests {
+
+ @Test
+ @DisplayName("issueKey = 'ABC-123' => issueKey = \"ABC-123\"")
+ void testEq() {
+ Qual q = simpleQual("key", Operator.EQUALS, stringValue("ABC-123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey = \"ABC-123\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey != 'XYZ-999' => issueKey != \"XYZ-999\"")
+ void testNe() {
+ Qual q = simpleQual("id", Operator.NOT_EQUALS, stringValue("XYZ-999"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey != \"XYZ-999\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey < 100 => issueKey < 100")
+ void testLt() {
+ Qual q = simpleQual("title", Operator.LESS_THAN, stringValue("100"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey < 100", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey <= 'ABC-1' => issueKey <= \"ABC-1\"")
+ void testLte() {
+ Qual q = simpleQual("key", Operator.LESS_THAN_OR_EQUAL, stringValue("ABC-1"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey <= \"ABC-1\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey > 123 => issueKey > 123 (unquoted if numeric)")
+ void testGt() {
+ Qual q = simpleQual("id", Operator.GREATER_THAN, stringValue("123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey > 123", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey >= \"XYZ-123\" => issueKey >= \"XYZ-123\"")
+ void testGte() {
+ Qual q = simpleQual("title", Operator.GREATER_THAN_OR_EQUAL, stringValue("XYZ-123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("issueKey >= \"XYZ-123\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("issueKey = null => not supported => Optional.empty()")
+ void testNull() {
+ Qual q = simpleQual("key", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty(), "Should be Optional.empty() because null is not supported for key");
+ }
+
+ @Test
+ @DisplayName("issueKey IN(...) and NOT IN(...)")
+ public void testIssueKeyInNotIn() {
+ // eq + isAny = true => issueKey IN (...)
+ Qual inQual =
+ listQual(
+ "key", Operator.EQUALS, true, List.of(stringValue("ABC-123"), stringValue("XYZ-888")));
+ // ne + isAny = false => issueKey NOT IN (...)
+ Qual notInQual =
+ listQual(
+ "title", Operator.NOT_EQUALS, false, List.of(stringValue("K-999"), stringValue("T-111")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("issueKey IN (\"ABC-123\", \"XYZ-888\")", jqls.get(0).orElseThrow());
+ assertEquals("issueKey NOT IN (\"K-999\", \"T-111\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Columns: "project_key", "project_id", "project_name" => "project"
+ // - Supported: =, !=, plus null checks => IS NULL, IS NOT NULL
+ // - Not supported: <, <=, >, >= => Optional.empty()
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName(
+ "Columns: project_key/id/name => project (==, !=, NULL checks supported, IN supported)")
+ class ProjectTests {
+
+ @Test
+ @DisplayName("project_key = 'ABC' => project = \"ABC\" (or unquoted if numeric)")
+ void testEq() {
+ Qual q = simpleQual("project_key", Operator.EQUALS, stringValue("ABC"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("project = \"ABC\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("project_id != 123 => project != 123")
+ void testNe() {
+ Qual q = simpleQual("project_id", Operator.NOT_EQUALS, stringValue("123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("project != 123", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("project_name = null => project IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("project_name", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("project IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("project_key != null => project IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("project_key", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("project IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("project_id < 'ABC' => not supported => Optional.empty()")
+ void testLt() {
+ Qual q = simpleQual("project_id", Operator.LESS_THAN, stringValue("ABC"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("project IN(...) and NOT IN(...)")
+ public void testProjectInNotIn() {
+ // eq + isAny => project IN(...)
+ Qual inQual =
+ listQual(
+ "project_key",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("MYPROJ"), stringValue("1234")));
+ // ne + !isAny => project NOT IN(...)
+ Qual notInQual =
+ listQual(
+ "project_id", Operator.NOT_EQUALS, false, List.of(stringValue("X"), stringValue("999")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("project IN (\"MYPROJ\", 1234)", jqls.get(0).orElseThrow());
+ assertEquals("project NOT IN (\"X\", 999)", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: "status"
+ // - eq, ne supported, plus null checks => IS NULL, IS NOT NULL
+ // - <, <=, >, >= => not supported => Optional.empty()
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: status => eq, ne, null checks only, IN supported")
+ class StatusTests {
+
+ @Test
+ @DisplayName("status = 'Open' => status = \"Open\"")
+ void testEq() {
+ Qual q = simpleQual("status", Operator.EQUALS, stringValue("Open"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("status = \"Open\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status != 'Closed' => status != \"Closed\"")
+ void testNe() {
+ Qual q = simpleQual("status", Operator.NOT_EQUALS, stringValue("Closed"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("status != \"Closed\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status = null => status IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("status", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("status IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status != null => status IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("status", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("status IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status < 'X' => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("status", Operator.LESS_THAN, stringValue("X"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("status IN(...) and NOT IN(...)")
+ public void testStatusInNotIn() {
+ // eq + isAny => status IN(...)
+ Qual inQual =
+ listQual(
+ "status",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("Open"), stringValue("In Progress")));
+
+ // ne + !isAny => status NOT IN(...)
+ Qual notInQual =
+ listQual(
+ "status",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("Closed"), stringValue("Resolved")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("status IN (\"Open\", \"In Progress\")", jqls.get(0).orElseThrow());
+ assertEquals("status NOT IN (\"Closed\", \"Resolved\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: "status_category" => statusCategory
+ // - eq, ne, null checks supported
+ // - <, <=, >, >= => not supported
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: status_category => eq, ne, null checks only, IN supported")
+ class StatusCategoryTests {
+
+ @Test
+ @DisplayName("status_category = 'To Do' => statusCategory = \"To Do\"")
+ void testEq() {
+ Qual q = simpleQual("status_category", Operator.EQUALS, stringValue("To Do"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("statusCategory = \"To Do\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status_category != 'Done' => statusCategory != \"Done\"")
+ void testNe() {
+ Qual q = simpleQual("status_category", Operator.NOT_EQUALS, stringValue("Done"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("statusCategory != \"Done\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status_category = null => statusCategory IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("status_category", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("statusCategory IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status_category != null => statusCategory IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("status_category", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("statusCategory IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("status_category < 'X' => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("status_category", Operator.LESS_THAN, stringValue("X"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("statusCategory IN(...) and NOT IN(...)")
+ public void testStatusCategoryInNotIn() {
+ Qual inQual =
+ listQual(
+ "status_category",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("To Do"), stringValue("In Progress")));
+ Qual notInQual =
+ listQual(
+ "status_category",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("Done"), stringValue("Closed")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("statusCategory IN (\"To Do\", \"In Progress\")", jqls.get(0).orElseThrow());
+ assertEquals("statusCategory NOT IN (\"Done\", \"Closed\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: "epic_key" => parent
+ // - eq, ne, null checks => supported
+ // - <, <=, >, >= => not supported
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: epic_key => parent => eq, ne, null checks")
+ class EpicKeyTests {
+
+ @Test
+ @DisplayName("epic_key = 'ABC-123' => parent = \"ABC-123\"")
+ void testEq() {
+ Qual q = simpleQual("epic_key", Operator.EQUALS, stringValue("ABC-123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("parent = \"ABC-123\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("epic_key != 'XYZ' => parent != \"XYZ\"")
+ void testNe() {
+ Qual q = simpleQual("epic_key", Operator.NOT_EQUALS, stringValue("XYZ"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("parent != \"XYZ\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("epic_key = null => parent IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("epic_key", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("parent IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("epic_key != null => parent IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("epic_key", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("parent IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("epic_key < 'xx' => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("epic_key", Operator.LESS_THAN, stringValue("xx"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("epic_key IN(...) and NOT IN(...) => parent IN/NOT IN(...)")
+ public void testEpicKeyInNotIn() {
+ Qual inQual =
+ listQual("epic_key", Operator.EQUALS, true, List.of(stringValue("E-1"), stringValue("E-2")));
+ Qual notInQual =
+ listQual(
+ "epic_key", Operator.NOT_EQUALS, false, List.of(stringValue("E-99"), stringValue("E-100")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("parent IN (\"E-1\", \"E-2\")", jqls.get(0).orElseThrow());
+ assertEquals("parent NOT IN (\"E-99\", \"E-100\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Columns: assignee_* => "assignee"
+ // eq, ne, null checks supported
+ // <, <=, >, >= => not supported
+ // IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Columns: assignee_* => assignee => eq, ne, null checks only, IN supported")
+ class AssigneeTests {
+
+ @Test
+ @DisplayName("assignee_email_address = 'bob@example.com' => assignee = \"bob@example.com\"")
+ void testEq() {
+ Qual q = simpleQual("assignee_email_address", Operator.EQUALS, stringValue("bob@example.com"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("assignee = \"bob@example.com\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("assignee_account_id != '1234' => assignee != 1234")
+ void testNe() {
+ Qual q = simpleQual("assignee_account_id", Operator.NOT_EQUALS, stringValue("1234"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("assignee != 1234", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("assignee_display_name = null => assignee IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("assignee_display_name", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("assignee IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("assignee_account_id != null => assignee IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("assignee_account_id", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("assignee IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("assignee_* < => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("assignee_email_address", Operator.LESS_THAN, stringValue("zzz"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("assignee IN(...) and NOT IN(...)")
+ public void testAssigneeInNotIn() {
+ Qual inQual =
+ listQual(
+ "assignee_display_name",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("Alice"), stringValue("Bob")));
+ Qual notInQual =
+ listQual(
+ "assignee_email_address",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("charlie@example.com"), stringValue("david@example.com")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("assignee IN (\"Alice\", \"Bob\")", jqls.get(0).orElseThrow());
+ assertEquals(
+ "assignee NOT IN (\"charlie@example.com\", \"david@example.com\")",
+ jqls.get(1).orElseThrow());
+ }
}
+ // --------------------------------------------------------------------------
+ // Columns: creator_* => "creator"
+ // eq, ne, null checks supported
+ // <, <=, >, >= => not supported
+ // IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Columns: creator_* => creator => eq, ne, null checks only, IN supported")
+ class CreatorTests {
+
+ @Test
+ @DisplayName("creator_email_address = 'alice@raw-labs.com' => creator = \"alice@raw-labs.com\"")
+ void testEq() {
+ Qual q = simpleQual("creator_email_address", Operator.EQUALS, stringValue("alice@raw-labs.com"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("creator = \"alice@raw-labs.com\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("creator_account_id != 'abcd' => creator != \"abcd\" (or numeric unquoted)")
+ void testNe() {
+ Qual q = simpleQual("creator_account_id", Operator.NOT_EQUALS, stringValue("abcd"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("creator != \"abcd\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("creator_display_name = null => creator IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("creator_display_name", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("creator IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("creator_email_address != null => creator IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("creator_email_address", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("creator IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("creator_* < => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("creator_account_id", Operator.LESS_THAN, stringValue("1234"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("creator IN(...) and NOT IN(...)")
+ public void testCreatorInNotIn() {
+ Qual inQual =
+ listQual(
+ "creator_email_address",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("alice@foo.com"), stringValue("bob@foo.com")));
+ Qual notInQual =
+ listQual(
+ "creator_account_id",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("1234"), stringValue("5678")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("creator IN (\"alice@foo.com\", \"bob@foo.com\")", jqls.get(0).orElseThrow());
+ // numeric => unquoted
+ assertEquals("creator NOT IN (1234, 5678)", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Columns: reporter_* => "reporter"
+ // eq, ne, null checks supported
+ // <, <=, >, >= => not supported
+ // IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Columns: reporter_* => reporter => eq, ne, null checks only, IN supported")
+ class ReporterTests {
+
+ @Test
+ @DisplayName("reporter_display_name = 'John Doe' => reporter = \"John Doe\"")
+ void testEq() {
+ Qual q = simpleQual("reporter_display_name", Operator.EQUALS, stringValue("John Doe"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("reporter = \"John Doe\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("reporter_display_name != 'John Doe' => reporter != \"John Doe\"")
+ void testNe() {
+ Qual q = simpleQual("reporter_display_name", Operator.NOT_EQUALS, stringValue("John Doe"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("reporter != \"John Doe\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("reporter_account_id = null => reporter IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("reporter_account_id", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("reporter IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("reporter_display_name != null => reporter IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("reporter_display_name", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("reporter IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("reporter_* < => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("reporter_display_name", Operator.LESS_THAN, stringValue("Z"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("reporter IN(...) and NOT IN(...)")
+ public void testReporterInNotIn() {
+ Qual inQual =
+ listQual(
+ "reporter_display_name",
+ Operator.EQUALS,
+ true,
+ List.of(stringValue("Tom"), stringValue("Jerry")));
+ Qual notInQual =
+ listQual(
+ "reporter_account_id",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("1234"), stringValue("1235")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("reporter IN (\"Tom\", \"Jerry\")", jqls.get(0).orElseThrow());
+ assertEquals("reporter NOT IN (1234, 1235)", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: created => "created"
+ // - All comparisons supported: =, !=, <, <=, >, >=
+ // - Null => created IS NULL, created IS NOT NULL
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: created => all comparisons, null checks")
+ class CreatedTests {
+
+ @Test
+ @DisplayName("created = '2024-01-01T10:00:00Z' => created = \"2024-01-01 10:00\"")
+ void testEq() {
+ Qual q = simpleQual("created", Operator.EQUALS, timestampValue(2024, 1, 1, 10, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created = \"2024-01-01 10:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created != '2024-02-02' => created != \"2024-02-02\" (Date => yyyy-MM-dd)")
+ void testNe() {
+ Qual q = simpleQual("created", Operator.NOT_EQUALS, dateValue(2024, 2, 2));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created != \"2024-02-02\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created < '2023-12-31T23:00' => created < \"2023-12-31 23:00\"")
+ void testLt() {
+ Qual q = simpleQual("created", Operator.LESS_THAN, timestampValue(2023, 12, 31, 23, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created < \"2023-12-31 23:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created <= '2024-05-01T10:00' => created <= \"2024-05-01 10:00\"")
+ void testLte() {
+ Qual q = simpleQual("created", Operator.LESS_THAN_OR_EQUAL, timestampValue(2024, 5, 1, 10, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created <= \"2024-05-01 10:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created > '2023-10-10T15:30' => created > \"2023-10-10 15:30\"")
+ void testGt() {
+ Qual q = simpleQual("created", Operator.GREATER_THAN, timestampValue(2023, 10, 10, 15, 30, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created > \"2023-10-10 15:30\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created >= '2023-01-01' => created >= \"2023-01-01\"")
+ void testGte() {
+ Qual q = simpleQual("created", Operator.GREATER_THAN_OR_EQUAL, dateValue(2023, 1, 1));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created >= \"2023-01-01\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created = null => created IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("created", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created != null => created IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("created", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("created IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("created IN(...) and NOT IN(...) for timestamps/dates")
+ public void testCreatedInNotIn() {
+ // eq + isAny => created IN(...)
+ // For demonstration, mix a timestampValue and a dateValue
+ Qual inQual =
+ listQual(
+ "created",
+ Operator.EQUALS,
+ true,
+ List.of(
+ timestampValue(2024, 1, 1, 10, 0, 0, 0),
+ dateValue(2024, 2, 5) // code will produce "2024-02-05"
+ ));
+ // ne + !isAny => created NOT IN(...)
+ Qual notInQual =
+ listQual(
+ "created",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(
+ timestampValue(2023, 12, 31, 23, 0, 0, 0),
+ timestampValue(2024, 6, 10, 12, 30, 0, 0)));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ // eq + isAny => created IN("2024-01-01 10:00", "2024-02-05")
+ assertEquals("created IN (\"2024-01-01 10:00\", \"2024-02-05\")", jqls.get(0).orElseThrow());
+ // ne + !isAny => created NOT IN("2023-12-31 23:00", "2024-06-10 12:30")
+ assertEquals(
+ "created NOT IN (\"2023-12-31 23:00\", \"2024-06-10 12:30\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: due_date => "due"
+ // - All comparisons, plus null checks
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: due_date => due => all comparisons, null checks")
+ class DueDateTests {
+
+ @Test
+ @DisplayName("due_date = null => due IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("due_date", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("due IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("due_date != null => due IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("due_date", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("due IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("due_date > '2024-10-01T10:00' => due > \"2024-10-01 10:00\"")
+ void testGt() {
+ Qual q = simpleQual("due_date", Operator.GREATER_THAN, timestampValue(2024, 10, 1, 10, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("due > \"2024-10-01 10:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("due_date IN(...) / NOT IN(...)")
+ public void testDueDateInNotIn() {
+ Qual inQual =
+ listQual(
+ "due_date",
+ Operator.EQUALS,
+ true,
+ List.of(dateValue(2025, 3, 10), timestampValue(2025, 3, 11, 8, 0, 0, 0)));
+ Qual notInQual =
+ listQual(
+ "due_date",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(
+ timestampValue(2025, 1, 1, 12, 0, 0, 0),
+ timestampValue(2025, 1, 2, 12, 0, 0, 0)));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("due IN (\"2025-03-10\", \"2025-03-11 08:00\")", jqls.get(0).orElseThrow());
+ assertEquals(
+ "due NOT IN (\"2025-01-01 12:00\", \"2025-01-02 12:00\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: due_date => "due"
+ // - All comparisons, plus null checks
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: updated => updated => all comparisons, null checks")
+ class UpdatedDateTests {
+
+ @Test
+ @DisplayName("updated = null => updated IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("updated", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("updated IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("updated != null => updated IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("updated", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("updated IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("updated > '2024-10-01T10:00' => updated > \"2024-10-01 10:00\"")
+ void testGt() {
+ Qual q = simpleQual("updated", Operator.GREATER_THAN, timestampValue(2024, 10, 1, 10, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("updated > \"2024-10-01 10:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("updated IN(...) / NOT IN(...)")
+ public void testupdatedDateInNotIn() {
+ Qual inQual =
+ listQual(
+ "updated",
+ Operator.EQUALS,
+ true,
+ List.of(dateValue(2025, 3, 10), timestampValue(2025, 3, 11, 8, 0, 0, 0)));
+ Qual notInQual =
+ listQual(
+ "updated",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(
+ timestampValue(2025, 1, 1, 12, 0, 0, 0),
+ timestampValue(2025, 1, 2, 12, 0, 0, 0)));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("updated IN (\"2025-03-10\", \"2025-03-11 08:00\")", jqls.get(0).orElseThrow());
+ assertEquals(
+ "updated NOT IN (\"2025-01-01 12:00\", \"2025-01-02 12:00\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: resolution_date => "resolved"
+ // - All comparisons, plus null checks
+ // - IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: resolution_date => resolved => all comparisons, null checks")
+ class ResolutionDateTests {
+ @Test
+ @DisplayName("resolution_date = 2023-01-10T12:00 => resolved = \"2023-01-10 12:00\"")
+ void testEq() {
+ Qual q =
+ simpleQual("resolution_date", Operator.EQUALS, timestampValue(2023, 1, 10, 12, 0, 0, 0));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("resolved = \"2023-01-10 12:00\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("resolution_date = null => resolved IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("resolution_date", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("resolved IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("resolution_date => resolved IN(...) / NOT IN(...)")
+ public void testResolutionDateInNotIn() {
+ // eq + isAny => resolved IN(...)
+ Qual inQual =
+ listQual(
+ "resolution_date",
+ Operator.EQUALS,
+ true,
+ List.of(
+ timestampValue(2024, 1, 1, 10, 0, 0, 0),
+ timestampValue(2024, 2, 10, 15, 30, 0, 0)));
+ // ne + !isAny => resolved NOT IN(...)
+ Qual notInQual =
+ listQual(
+ "resolution_date",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(dateValue(2024, 3, 1), timestampValue(2024, 4, 15, 9, 45, 0, 0)));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals(
+ "resolved IN (\"2024-01-01 10:00\", \"2024-02-10 15:30\")", jqls.get(0).orElseThrow());
+ assertEquals(
+ "resolved NOT IN (\"2024-03-01\", \"2024-04-15 09:45\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: summary => special eq => summary ~, ne => summary !~, null => IS/IS NOT NULL
+ // - No <, <=, >, >=
+ // - = is supported using ~ (contains) as an approximation
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: summary => eq => summary ~, ne => summary !~, null checks, others => empty")
+ class SummaryTests {
+ @Test
+ @DisplayName("summary = 'foo' => summary ~ \"foo\"")
+ void testEq() {
+ Qual q = simpleQual("summary", Operator.EQUALS, stringValue("foo"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("summary ~ \"foo\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("summary != 'bar' => summary !~ \"bar\"")
+ void testNe() {
+ Qual q = simpleQual("summary", Operator.NOT_EQUALS, stringValue("bar"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("summary !~ \"bar\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("summary = null => summary IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("summary", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("summary IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("summary != null => summary IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("summary", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("summary IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("summary < 'x' => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("summary", Operator.LESS_THAN, stringValue("x"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("summary IN(...) / NOT IN(...) not supported")
+ public void testSummaryInNotIn() {
+ // eq + isAny => resolved IN(...)
+ Qual inQual =
+ listQual("summary", Operator.EQUALS, true, List.of(stringValue("foo"), stringValue("bar")));
+ // ne + !isAny => resolved NOT IN(...)
+ Qual notInQual =
+ listQual("summary", Operator.NOT_EQUALS, false, List.of(stringValue("baz"), stringValue("qux")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assert (jqls.get(0).isEmpty());
+ assert (jqls.get(1).isEmpty());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: type => eq, ne, null checks. < etc. => not supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: type => eq, ne, null checks only")
+ class TypeTests {
+ @Test
+ @DisplayName("type = 'Bug' => type = \"Bug\"")
+ void testEq() {
+ Qual q = simpleQual("type", Operator.EQUALS, stringValue("Bug"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("type = \"Bug\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("type != 'Task' => type != \"Task\"")
+ void testNe() {
+ Qual q = simpleQual("type", Operator.NOT_EQUALS, stringValue("Task"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("type != \"Task\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("type = null => type IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("type", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("type IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("type != null => type IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("type", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("type IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("type < => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("type", Operator.LESS_THAN, stringValue("X"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("type IN(...) and NOT IN(...)")
+ public void testTypeInNotIn() {
+ Qual inQual =
+ listQual("type", Operator.EQUALS, true, List.of(stringValue("Bug"), stringValue("Task")));
+ Qual notInQual =
+ listQual("type", Operator.NOT_EQUALS, false, List.of(stringValue("Story"), stringValue("Epic")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("type IN (\"Bug\", \"Task\")", jqls.get(0).orElseThrow());
+ assertEquals("type NOT IN (\"Story\", \"Epic\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Column: priority => eq, ne, null checks only
+ // We don't support > and < because the priority values are not ordered like strings
+ // IN supported
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Column: priority => eq, ne, null checks only")
+ class PriorityTests {
+ @Test
+ @DisplayName("priority = 'High' => priority = \"High\"")
+ void testEq() {
+ Qual q = simpleQual("priority", Operator.EQUALS, stringValue("High"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("priority = \"High\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("priority != 'Low' => priority != \"Low\"")
+ void testNe() {
+ Qual q = simpleQual("priority", Operator.NOT_EQUALS, stringValue("Low"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("priority != \"Low\"", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("priority = null => priority IS NULL")
+ void testEqNull() {
+ Qual q = simpleQual("priority", Operator.EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("priority IS NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("priority != null => priority IS NOT NULL")
+ void testNeNull() {
+ Qual q = simpleQual("priority", Operator.NOT_EQUALS, nullValue());
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertEquals("priority IS NOT NULL", res.orElseThrow());
+ }
+
+ @Test
+ @DisplayName("priority < => not supported => Optional.empty()")
+ void testUnsupportedLt() {
+ Qual q = simpleQual("priority", Operator.LESS_THAN, stringValue("X"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("priority IN(...) and NOT IN(...)")
+ public void testPriorityInNotIn() {
+ Qual inQual =
+ listQual(
+ "priority", Operator.EQUALS, true, List.of(stringValue("High"), stringValue("Medium")));
+ Qual notInQual =
+ listQual(
+ "priority",
+ Operator.NOT_EQUALS,
+ false,
+ List.of(stringValue("Low"), stringValue("Critical")));
+
+ var jqls = jqlBuilder.mkJql(List.of(inQual, notInQual));
+ assertEquals("priority IN (\"High\", \"Medium\")", jqls.get(0).orElseThrow());
+ assertEquals("priority NOT IN (\"Low\", \"Critical\")", jqls.get(1).orElseThrow());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Columns: labels, components, fields, tags, sprint_ids, sprint_names, description, self
+ // => not supported => always Optional.empty()
+ // --------------------------------------------------------------------------
+ @Nested
+ @DisplayName("Unsupported columns => always Optional.empty()")
+ class UnsupportedColumnsTests {
+
+ @Test
+ @DisplayName("labels => Optional.empty()")
+ void testLabels() {
+ Qual q = simpleQual("labels", Operator.EQUALS, stringValue("something"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("components => Optional.empty()")
+ void testComponents() {
+ Qual q = simpleQual("components", Operator.EQUALS, stringValue("UI"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("fields => Optional.empty()")
+ void testFields() {
+ Qual q = simpleQual("fields", Operator.EQUALS, stringValue("anything"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("tags => Optional.empty()")
+ void testTags() {
+ Qual q = simpleQual("tags", Operator.EQUALS, stringValue("myTag"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("sprint_ids => Optional.empty()")
+ void testSprintIds() {
+ Qual q = simpleQual("sprint_ids", Operator.EQUALS, stringValue("123"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("sprint_names => Optional.empty()")
+ void testSprintNames() {
+ Qual q = simpleQual("sprint_names", Operator.EQUALS, stringValue("Sprint 1"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("description => Optional.empty()")
+ void testDescription() {
+ Qual q = simpleQual("description", Operator.EQUALS, stringValue("some text"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+
+ @Test
+ @DisplayName("self => Optional.empty()")
+ void testSelf() {
+ Qual q = simpleQual("self", Operator.EQUALS, stringValue("http://whatever"));
+ Optional res = jqlBuilder.mkJql(List.of(q)).get(0);
+ assertTrue(res.isEmpty());
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Combined usage example
+ // --------------------------------------------------------------------------
@Test
- @DisplayName("Should generate proper JQL queries")
- public void shouldGenerateJqlQuery() {
- String result =
- DASJiraJqlQueryBuilder.buildJqlQuery(
+ @DisplayName("Combined usage => multiple columns => buildJqlQuery joined by AND")
+ void testCombinedBuild() {
+ // 1) key < "ABC-123" => issueKey < "ABC-123"
+ Qual issueKeyLt = simpleQual("key", Operator.LESS_THAN, stringValue("ABC-123"));
+ // 2) project_name != null => project IS NOT NULL
+ Qual projectNeNull = simpleQual("project_name", Operator.NOT_EQUALS, nullValue());
+ // 3) status = 'Open' => status = "Open"
+ Qual statusEq = simpleQual("status", Operator.EQUALS, stringValue("Open"));
+ // 4) labels => unsupported => omitted
+
+ String jql =
+ jqlBuilder.buildJqlQuery(
List.of(
- createEq(
- valueFactory.createValue(new ValueTypeTuple("DAS", createStringType())),
- "summary"),
- createGte(
- valueFactory.createValue(
- new ValueTypeTuple("2021-01-01T00:00:00Z", createTimestampType())),
- "created_date"),
- createLte(
- valueFactory.createValue(
- new ValueTypeTuple("2021-01-01T00:00:00Z", createTimestampType())),
- "due_date")));
-
- assertEquals(
- "summary = \"DAS\" AND created >= \"2021-01-01 00:00\" AND due <= \"2021-01-01 00:00\"", result);
+ issueKeyLt,
+ projectNeNull,
+ statusEq,
+ simpleQual("labels", Operator.EQUALS, stringValue("something"))));
+
+ // Expect: "issueKey < \"ABC-123\" AND project IS NOT NULL AND status = \"Open\""
+ // (labels is dropped)
+ String expected = "issueKey < \"ABC-123\" AND project IS NOT NULL AND status = \"Open\"";
+ assertEquals(expected, jql);
}
}
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraTableManagerTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraTableManagerTest.java
deleted file mode 100644
index a87d208..0000000
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/DASJiraTableManagerTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.rawlabs.das.jira.tables;
-
-import com.rawlabs.das.jira.initializer.DASJiraInitializer;
-import com.rawlabs.das.jira.tables.definitions.DASJiraAdvancedSettingsTable;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import java.util.Map;
-import java.util.Optional;
-
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-@DisplayName("DAS Jira Table Manager Test")
-public class DASJiraTableManagerTest {
-
- public static final DASJiraTableManager dasJiraTableManager =
- new DASJiraTableManager(Map.of(), null, null);
-
- @Test
- @DisplayName("Table manager initialization test")
- public void testGetTableDefinition() {
- assertNotNull(dasJiraTableManager);
- Optional table =
- dasJiraTableManager.getTable(DASJiraAdvancedSettingsTable.TABLE_NAME);
- assert (table.isPresent());
- int tableDefinitionsCount = dasJiraTableManager.getTableDefinitions().size();
- assertNotEquals(0, tableDefinitionsCount);
- int tablesCount = dasJiraTableManager.getTables().size();
- assertNotEquals(0, tablesCount);
- }
-}
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraBacklogIssueTableTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraBacklogIssueTableTest.java
index f7a74ed..8a4c6be 100644
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraBacklogIssueTableTest.java
+++ b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraBacklogIssueTableTest.java
@@ -17,15 +17,16 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.ZoneId;
+import java.util.Map;
@DisplayName("DAS Jira Backlog Issue Table Test")
public class DASJiraBacklogIssueTableTest extends BaseMockTest {
@Mock static BoardApi boardApi;
- @InjectMocks DASJiraBacklogIssueTable dasJiraBacklogIssueTable;
+ private static DASJiraBacklogIssueTable dasJiraBacklogIssueTable;
private static SearchResults searchResults;
@@ -40,6 +41,12 @@ static void beforeAll() throws IOException {
@BeforeEach
void setUp() throws ApiException {
DASJiraBoardTableTest.configBeforeEach(boardApi);
+ dasJiraBacklogIssueTable =
+ new DASJiraBacklogIssueTable(
+ Map.of("timezone", "UTC"), // The options
+ ZoneId.of("UTC"), // The localZoneId
+ ZoneId.of("UTC"), // The jiraZoneId
+ boardApi);
when(boardApi.getIssuesForBacklog(any(), any(), any(), any(), any(), any(), any()))
.thenReturn(searchResults);
}
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueCommentTableTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueCommentTableTest.java
index f4e79a0..5edb791 100644
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueCommentTableTest.java
+++ b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueCommentTableTest.java
@@ -19,16 +19,17 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.ZoneId;
+import java.util.Map;
@DisplayName("DAS Jira Issue Comment Table Test")
public class DASJiraIssueCommentTableTest extends BaseMockTest {
@Mock static IssueCommentsApi issueCommentsApi;
@Mock static IssueSearchApi issueSearchApi;
- @InjectMocks DASJiraIssueCommentTable dasJiraIssueCommentTable;
+ private DASJiraIssueCommentTable dasJiraIssueCommentTable;
private static PageOfComments pageOfComments;
@@ -42,6 +43,15 @@ static void beforeAll() throws IOException {
@BeforeEach
void setUp() throws ApiException {
DASJiraIssueTableTest.configBeforeEach(issueSearchApi);
+ dasJiraIssueCommentTable =
+ new DASJiraIssueCommentTable(
+ Map.of("timezone", "UTC"), // The options
+ ZoneId.of("UTC"), // The localZoneId
+ ZoneId.of("UTC"), // The jiraZoneId
+ issueCommentsApi,
+ issueSearchApi,
+ null // issuesApi
+ );
when(issueCommentsApi.getComments(any(), any(), any(), any(), any()))
.thenReturn(pageOfComments);
}
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueTableTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueTableTest.java
index e6ec878..e8ad975 100644
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueTableTest.java
+++ b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueTableTest.java
@@ -17,14 +17,15 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.ZoneId;
+import java.util.Map;
@DisplayName("DAS Jira Issue Table Test")
public class DASJiraIssueTableTest extends BaseMockTest {
- @Mock static IssueSearchApi issueSearchApi;
+ @Mock private IssueSearchApi issueSearchApi;
- @InjectMocks DASJiraIssueTable dasJiraIssueTable;
+ private static DASJiraIssueTable dasJiraIssueTable;
private static SearchResults searchResults;
@@ -47,6 +48,14 @@ public static void configBeforeEach(IssueSearchApi issueSearchApi) throws ApiExc
@BeforeEach
void setUp() throws ApiException {
configBeforeEach(issueSearchApi);
+ dasJiraIssueTable =
+ new DASJiraIssueTable(
+ Map.of("timezone", "UTC"), // The options
+ ZoneId.of("UTC"), // The localZoneId
+ ZoneId.of("UTC"), // The jiraZoneId (whatever you need in test)
+ issueSearchApi,
+ null // issuesApi
+ );
}
@DisplayName("Get issues")
diff --git a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueWorklogTableTest.java b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueWorklogTableTest.java
index 79d4c9a..a66f327 100644
--- a/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueWorklogTableTest.java
+++ b/das-jira-connector/src/test/java/com/rawlabs/das/jira/tables/defnitions/DASJiraIssueWorklogTableTest.java
@@ -18,8 +18,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.ZoneId;
+import java.util.Map;
@DisplayName("DAS Jira Issue Worklog Table Test")
public class DASJiraIssueWorklogTableTest extends BaseMockTest {
@@ -27,7 +28,7 @@ public class DASJiraIssueWorklogTableTest extends BaseMockTest {
@Mock static IssueWorklogsApi issueWorklogsApi;
@Mock static IssueSearchApi issueSearchApi;
- @InjectMocks DASJiraIssueWorklogTable dasJiraIssueWorklogTable;
+ DASJiraIssueWorklogTable dasJiraIssueWorklogTable;
private static PageOfWorklogs pageOfWorklogs;
@@ -41,6 +42,15 @@ static void beforeAll() throws IOException {
@BeforeEach
void setUp() throws ApiException {
DASJiraIssueTableTest.configBeforeEach(issueSearchApi);
+ dasJiraIssueWorklogTable =
+ new DASJiraIssueWorklogTable(
+ Map.of("timezone", "UTC"), // The options
+ ZoneId.of("UTC"), // The localZoneId
+ ZoneId.of("UTC"), // The jiraZoneId
+ issueWorklogsApi,
+ issueSearchApi,
+ null // issuesApi
+ );
when(issueWorklogsApi.getIssueWorklog(any(), any(), any(), any(), any(), any()))
.thenReturn(pageOfWorklogs);
}