-prevent api classes from accessing internal classes
-disable architecture test for mapper classes / package
-added architecture tests for exceptions and api/internal
-defined exceptional classes: TaskanaHistory* and BulkOperationResults
This commit is contained in:
BerndBreier 2020-01-31 15:41:32 +01:00
parent 4423854822
commit 77663f02ca
44 changed files with 126 additions and 75 deletions

View File

@ -3,7 +3,9 @@ package pro.taskana.rest.resource;
import javax.validation.constraints.NotNull;
import org.springframework.hateoas.ResourceSupport;
/** Resource class for {@link pro.taskana.history.api.TaskanaHistoryEvent}. */
import pro.taskana.history.api.TaskanaHistoryEvent;
/** Resource class for {@link TaskanaHistoryEvent}. */
public class TaskHistoryEventResource extends ResourceSupport {
@NotNull private String taskHistoryEventId;

View File

@ -1,12 +1,9 @@
package pro.taskana.classification.api;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.time.Instant;
import pro.taskana.classification.internal.ClassificationImpl;
/** Interface used to specify the Classification-Model. */
@JsonDeserialize(as = ClassificationImpl.class)
// @JsonDeserialize(as = ClassificationImpl.class)
public interface Classification extends ClassificationSummary {
/**

View File

@ -20,13 +20,13 @@ import pro.taskana.classification.api.ClassificationSummary;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.ClassificationInUseException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.common.internal.jobs.ClassificationChangedJob;
import pro.taskana.common.internal.jobs.ScheduledJob;
import pro.taskana.common.internal.util.IdGenerator;
import pro.taskana.task.api.TaskSummary;
import pro.taskana.task.api.TaskanaRole;

View File

@ -1,7 +1,5 @@
package pro.taskana.common.api;
import pro.taskana.common.internal.jobs.ScheduledJob;
/** Service to manage the TASKANA jobs. */
public interface JobService {

View File

@ -1,4 +1,4 @@
package pro.taskana.common.internal.jobs;
package pro.taskana.common.api;
import java.time.Instant;
import java.util.Map;

View File

@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import pro.taskana.common.internal.jobs.ScheduledJob;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.internal.persistence.MapTypeHandler;
/** This class is the mybatis mapping of the JOB table. */

View File

@ -6,7 +6,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.JobService;
import pro.taskana.common.internal.jobs.ScheduledJob;
import pro.taskana.common.api.ScheduledJob;
/** Controls all job activities. */
public class JobServiceImpl implements JobService {

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.TaskanaEngineImpl;

View File

@ -7,6 +7,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;

View File

@ -7,6 +7,7 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.JobServiceImpl;

View File

@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.common.api.BaseQuery;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.TimeInterval;
import pro.taskana.common.api.exceptions.InvalidArgumentException;

View File

@ -6,6 +6,7 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.internal.transaction.TaskanaTransactionProvider;

View File

@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import pro.taskana.common.api.BaseQuery;
import pro.taskana.common.api.BulkOperationResults;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.TaskanaEngine;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;

View File

@ -4,9 +4,9 @@ import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.structure.Report;
/**
* A CategoryReport contains the total numbers of tasks of the respective category as well as the

View File

@ -4,13 +4,13 @@ import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.row.DetailedClassificationRow;
import pro.taskana.report.api.row.FoldableRow;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.DetailedMonitorQueryItem;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.row.DetailedClassificationRow;
import pro.taskana.report.internal.row.FoldableRow;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.internal.structure.Row;
/**
* The ClassificationReport extends the Report. The {@link Row}s of the ClassificationReport are

View File

@ -4,9 +4,9 @@ import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.structure.Report;
/**
* A CustomFieldValueReport contains the total numbers of tasks of the respective custom field as

View File

@ -6,9 +6,9 @@ import java.util.stream.Stream;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.header.TaskStatusColumnHeader;
import pro.taskana.report.internal.item.TaskQueryItem;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.task.api.TaskState;
/**

View File

@ -5,10 +5,10 @@ import java.util.Map;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.SelectedItem;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.AgeQueryItem;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.task.api.CustomField;
import pro.taskana.task.api.TaskState;

View File

@ -4,10 +4,10 @@ import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.row.TimestampRow;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.TimestampQueryItem;
import pro.taskana.report.internal.row.TimestampRow;
import pro.taskana.report.internal.structure.Report;
/** A {@link TimestampReport} displays created and competed tasks for a specific dates. */
public class TimestampReport extends Report<TimestampQueryItem, TimeIntervalColumnHeader> {

View File

@ -4,10 +4,10 @@ import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.internal.CombinedClassificationFilter;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.structure.Report;
/**
* A WorkbasketReport contains the total numbers of tasks of the respective workbasket as well as

View File

@ -1,8 +1,8 @@
package pro.taskana.report.internal.row;
package pro.taskana.report.api.row;
import pro.taskana.report.api.ClassificationReport;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.item.DetailedMonitorQueryItem;
import pro.taskana.report.internal.structure.Row;
/**
* Represents a single Row inside {@link ClassificationReport.DetailedClassificationReport}. The

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.row;
package pro.taskana.report.api.row;
import java.util.LinkedHashMap;
import java.util.Map;
@ -6,9 +6,9 @@ import java.util.Set;
import java.util.function.Function;
import pro.taskana.common.internal.util.LoggerUtils;
import pro.taskana.report.internal.structure.QueryItem;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.internal.structure.Row;
import pro.taskana.report.api.structure.QueryItem;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.api.structure.Row;
/**
* The FoldableRow extends the {@link SingleRow}. In contrast to the {@link SingleRow} the

View File

@ -1,9 +1,9 @@
package pro.taskana.report.internal.row;
package pro.taskana.report.api.row;
import pro.taskana.report.internal.structure.ColumnHeader;
import pro.taskana.report.internal.structure.QueryItem;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.internal.structure.Row;
import pro.taskana.report.api.structure.ColumnHeader;
import pro.taskana.report.api.structure.QueryItem;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.api.structure.Row;
/**
* A SingleRow represents a single row in a {@link Report}. It contains an array of cells whose

View File

@ -1,8 +1,8 @@
package pro.taskana.report.internal.row;
package pro.taskana.report.api.row;
import pro.taskana.report.api.TimestampReport;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.item.TimestampQueryItem;
import pro.taskana.report.internal.structure.Row;
/**
* A single Row inside the {@link TimestampReport}. It contains 4 sub-rows for each org level

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.structure;
package pro.taskana.report.api.structure;
/**
* A ColumnHeader is an element of a {@link Report}. It determines weather a given &lt;Item&gt;

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.structure;
package pro.taskana.report.api.structure;
/**
* A QueryItem is en entity on which a {@link Report} is based on. Its value will be added to the

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.structure;
package pro.taskana.report.api.structure;
/**
* The QueryItemPreprocessor is used when adding {@link QueryItem}s into a {@link Report}. It

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.structure;
package pro.taskana.report.api.structure;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@ -8,7 +8,7 @@ import java.util.Set;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.internal.row.SingleRow;
import pro.taskana.report.api.row.SingleRow;
/**
* A Report represents an abstract table that consists of {@link Row}s and a list of

View File

@ -1,4 +1,4 @@
package pro.taskana.report.internal.structure;
package pro.taskana.report.api.structure;
/**
* Representation of a row in a {@link Report}. It contains an array of cells whose index

View File

@ -1,7 +1,7 @@
package pro.taskana.report.internal.header;
import pro.taskana.report.api.structure.ColumnHeader;
import pro.taskana.report.internal.item.TaskQueryItem;
import pro.taskana.report.internal.structure.ColumnHeader;
import pro.taskana.task.api.TaskState;
/** The TaskStatusColumnHeader represents a column for each {@link TaskState}. */

View File

@ -5,8 +5,8 @@ import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import pro.taskana.report.api.structure.ColumnHeader;
import pro.taskana.report.internal.item.AgeQueryItem;
import pro.taskana.report.internal.structure.ColumnHeader;
/**
* A TimeIntervalColumnHeader has a lower and an upper age limit which subdivide the count of tasks

View File

@ -1,6 +1,6 @@
package pro.taskana.report.internal.item;
import pro.taskana.report.internal.structure.QueryItem;
import pro.taskana.report.api.structure.QueryItem;
/**
* The MonitorQueryItem entity contains the number of tasks for a key (e.g. workbasketKey) and age

View File

@ -1,6 +1,6 @@
package pro.taskana.report.internal.item;
import pro.taskana.report.internal.structure.QueryItem;
import pro.taskana.report.api.structure.QueryItem;
import pro.taskana.task.api.TaskState;
/**

View File

@ -3,10 +3,10 @@ package pro.taskana.report.internal.preprocessor;
import java.util.List;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.report.api.structure.QueryItemPreprocessor;
import pro.taskana.report.internal.DaysToWorkingDaysConverter;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.AgeQueryItem;
import pro.taskana.report.internal.structure.QueryItemPreprocessor;
/**
* Uses {@link DaysToWorkingDaysConverter} to convert an &lt;I&gt;s age to working days.

View File

@ -1,12 +1,9 @@
package pro.taskana.workbasket.api;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.time.Instant;
import pro.taskana.workbasket.internal.WorkbasketImpl;
/** Workbasket entity interface. */
@JsonDeserialize(as = WorkbasketImpl.class)
// @JsonDeserialize(as = WorkbasketImpl.class)
public interface Workbasket {
/**

View File

@ -19,13 +19,13 @@ import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.common.api.JobService;
import pro.taskana.common.api.ScheduledJob;
import pro.taskana.common.api.exceptions.AttachmentPersistenceException;
import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.JobServiceImpl;
import pro.taskana.common.internal.jobs.ScheduledJob;
import pro.taskana.security.JaasExtension;
import pro.taskana.security.WithAccessId;
import pro.taskana.task.api.Task;

View File

@ -20,10 +20,10 @@ import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.ClassificationReport.DetailedClassificationReport;
import pro.taskana.report.api.TaskMonitorService;
import pro.taskana.report.api.row.FoldableRow;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.DetailedMonitorQueryItem;
import pro.taskana.report.internal.row.FoldableRow;
import pro.taskana.report.internal.structure.Row;
import pro.taskana.security.JaasExtension;
import pro.taskana.security.WithAccessId;
import pro.taskana.task.api.CustomField;

View File

@ -17,9 +17,9 @@ import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.report.api.TaskMonitorService;
import pro.taskana.report.api.TaskStatusReport;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.header.TaskStatusColumnHeader;
import pro.taskana.report.internal.item.TaskQueryItem;
import pro.taskana.report.internal.structure.Row;
import pro.taskana.security.JaasExtension;
import pro.taskana.security.WithAccessId;
import pro.taskana.task.api.TaskState;

View File

@ -14,10 +14,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import pro.taskana.report.api.TaskMonitorService;
import pro.taskana.report.api.TimestampReport;
import pro.taskana.report.api.row.SingleRow;
import pro.taskana.report.api.row.TimestampRow;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.TimestampQueryItem;
import pro.taskana.report.internal.row.SingleRow;
import pro.taskana.report.internal.row.TimestampRow;
import pro.taskana.security.JaasExtension;
import pro.taskana.security.WithAccessId;

View File

@ -5,10 +5,12 @@ import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import jdk.nashorn.internal.ir.annotations.Ignore;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -17,17 +19,34 @@ import org.junit.jupiter.api.Test;
* Test architecture of classes in taskana. For more info and examples see
* https://www.archunit.org/userguide/html/000_Index.html
*/
@SuppressWarnings({"checkstyle:EmptyLineSeparator"})
class ArchitectureTest {
private static JavaClasses importedClasses;
DescribedPredicate<JavaClass> doNotContain(String... namesToBeExcluded) {
return new DescribedPredicate<JavaClass>("should not be checked") {
@Override
public boolean apply(JavaClass input) {
String matchingClassName =
Arrays.asList(namesToBeExcluded).stream()
.filter(name -> input.getName().contains(name))
.findFirst()
.orElse(null);
return (matchingClassName == null);
};
};
}
@BeforeAll
static void init() {
static void init() throws ClassNotFoundException {
// time intensive operation should only be done once
importedClasses = new ClassFileImporter().importPackages("pro.taskana");
}
@Ignore
@Test
@Disabled
void mapperShouldBePlacedInMappingsPackage() {
ArchRule myRule =
classes()
@ -39,10 +58,36 @@ class ArchitectureTest {
myRule.check(importedClasses);
}
@Test
void apiClassesShouldNotDependOnInternalClasses() {
ArchRule myRule =
classes()
.that()
.resideInAPackage("..api")
.should()
.onlyDependOnClassesThat()
.resideOutsideOfPackage("..internal..");
myRule.check(importedClasses.that(doNotContain("TaskanaHistory",
"BulkOperationResults")));
}
@Test
void exceptionsShouldBePlacedInExceptionPackage() {
ArchRule myRule =
classes()
.that()
.haveSimpleNameEndingWith("Exception")
.should()
.resideInAPackage("..exceptions..");
myRule.check(importedClasses);
}
@Test
void onlyExceptionsShouldResideInExceptionPackage() {
ArchRule myRule =
classes().that().resideInAPackage("..exceptions").should().beAssignableTo(Throwable.class);
classes().that().resideInAPackage("..exceptions").should()
.beAssignableTo(Throwable.class);
myRule.check(importedClasses);
}
@ -59,14 +104,20 @@ class ArchitectureTest {
@Test
@Disabled
void freeOfCycles() {
ArchRule myRule = slices().matching("pro.taskana.(*)..").should().beFreeOfCycles();
ArchRule myRule = slices().matching("pro.taskana.(*)..")
.should().beFreeOfCycles();
myRule.check(importedClasses);
}
@Test
@Disabled
void freeOfCyclicDependencies() {
ArchRule myRule = slices().matching("pro.taskana.(*)..").should().notDependOnEachOther();
ArchRule myRule = slices().matching("pro.taskana.(*)..").should()
.notDependOnEachOther();
myRule.check(importedClasses);
}
private boolean containsName(JavaClass input, String name) {
return !input.getName().contains(name);
}
}

View File

@ -29,10 +29,10 @@ import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.internal.InternalTaskanaEngine;
import pro.taskana.report.api.ClassificationReport;
import pro.taskana.report.api.ClassificationReport.DetailedClassificationReport;
import pro.taskana.report.api.row.FoldableRow;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.DetailedMonitorQueryItem;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.row.FoldableRow;
import pro.taskana.task.api.CustomField;
import pro.taskana.task.api.TaskState;

View File

@ -12,11 +12,11 @@ import java.util.stream.IntStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import pro.taskana.report.api.structure.QueryItemPreprocessor;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.api.structure.Row;
import pro.taskana.report.internal.header.TimeIntervalColumnHeader;
import pro.taskana.report.internal.item.MonitorQueryItem;
import pro.taskana.report.internal.structure.QueryItemPreprocessor;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.internal.structure.Row;
/** Tests for {@link Report}. */
class ReportTest {

View File

@ -4,8 +4,8 @@ import java.util.Arrays;
import java.util.List;
import org.springframework.hateoas.ResourceSupport;
import pro.taskana.report.internal.row.SingleRow;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.api.row.SingleRow;
import pro.taskana.report.api.structure.Report;
/** Resource class for {@link Report}. */
public class ReportResource extends ResourceSupport {

View File

@ -18,12 +18,12 @@ import pro.taskana.report.api.ClassificationReport;
import pro.taskana.report.api.TaskStatusReport;
import pro.taskana.report.api.TimestampReport;
import pro.taskana.report.api.WorkbasketReport;
import pro.taskana.report.internal.row.FoldableRow;
import pro.taskana.report.internal.row.SingleRow;
import pro.taskana.report.internal.structure.ColumnHeader;
import pro.taskana.report.internal.structure.QueryItem;
import pro.taskana.report.internal.structure.Report;
import pro.taskana.report.internal.structure.Row;
import pro.taskana.report.api.row.FoldableRow;
import pro.taskana.report.api.row.SingleRow;
import pro.taskana.report.api.structure.ColumnHeader;
import pro.taskana.report.api.structure.QueryItem;
import pro.taskana.report.api.structure.Report;
import pro.taskana.report.api.structure.Row;
import pro.taskana.rest.MonitorController;
import pro.taskana.task.api.TaskState;