From 109cf22875d6f7ed90216101adada940aa9b41b4 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Wed, 19 Dec 2018 15:49:58 -0800 Subject: [PATCH 1/6] Initial prototype design of an IntersectionReport --- .../nuix/superutilities/SuperUtilities.java | 2 +- .../reporting/ColumnValueGenerator.java | 15 ++ .../reporting/IntersectionReport.java | 148 ++++++++++++++++++ .../superutilities/reporting/NamedQuery.java | 29 ++++ .../ScriptedColumnValueGenerator.java | 19 +++ .../reporting/SimpleWorksheet.java | 59 +++++++ .../superutilities/reporting/SimpleXlsx.java | 71 +++++++++ RubyTests/IntersectionReportTest.rb | 48 ++++++ 8 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java create mode 100644 RubyTests/IntersectionReportTest.rb diff --git a/Java/src/main/java/com/nuix/superutilities/SuperUtilities.java b/Java/src/main/java/com/nuix/superutilities/SuperUtilities.java index 89312c9..112c708 100644 --- a/Java/src/main/java/com/nuix/superutilities/SuperUtilities.java +++ b/Java/src/main/java/com/nuix/superutilities/SuperUtilities.java @@ -187,7 +187,7 @@ public BulkCaseProcessor createBulkCaseProcessor(){ } /*** - * Saves a diagnostics zip file (similar to same operation in the GUI) + * Saves a diagnostics zip file (similar to same operation in the workbench GUI) * @param zipFile File object specifying where the zip file should be saved to. */ public void saveDiagnostics(File zipFile){ diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java new file mode 100644 index 0000000..da7f0b7 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java @@ -0,0 +1,15 @@ +package com.nuix.superutilities.reporting; + +import nuix.Case; + +public class ColumnValueGenerator { + protected String label = "Value"; + public Object generateValue(Case nuixCase, String query) { return "Not Implemented"; } + + public String getLabel() { + return label; + } + public void setLabel(String label) { + this.label = label; + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java new file mode 100644 index 0000000..5131580 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java @@ -0,0 +1,148 @@ +package com.nuix.superutilities.reporting; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import nuix.Case; + +public class IntersectionReport { + + private List rowCriteria = new ArrayList(); + private List colCriteria = new ArrayList(); + private List valueGenerators = new ArrayList(); + + private String rowCategoryLabel = "Term"; + private String colPrimaryCategoryLabel = "Column Category"; + + private SimpleXlsx xlsx = null; + + private String parenExpression(String expression) { + return "("+expression+")"; + } + + private String andExpressions(String... expressions) { + return String.join(" AND ", expressions); + } + + public IntersectionReport(String file) throws Exception { + xlsx = new SimpleXlsx(file); + } + + public void generate(Case nuixCase, String sheetName) throws Exception { + SimpleWorksheet sheet = xlsx.getSheet(sheetName); + + List parenRowCriteria = rowCriteria.stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); + List parenColCriteria = colCriteria.stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); + + List rowValues = new ArrayList(); + + // Start out by building out headers + sheet.setValue(0, 0, colPrimaryCategoryLabel); + sheet.setValue(1, 0, rowCategoryLabel); + for (int c = 0; c < colCriteria.size(); c++) { + // First Row + String colCriterion = colCriteria.get(c).getName(); + int col = 1 + (c * valueGenerators.size()); + sheet.setValue(0, col, colCriterion); + sheet.mergeCols(0, col, valueGenerators.size()); + + // Second Row + for (int sc = 0; sc < valueGenerators.size(); sc++) { + int subCol = col + sc; + sheet.setValue(1, subCol, valueGenerators.get(sc).getLabel()); + } + } + sheet.setCurrentRow(sheet.getCurrentRow()+2); + + for (int r = 0; r < rowCriteria.size(); r++) { + String parenRowCriterion = parenRowCriteria.get(r); + + rowValues.clear(); + rowValues.add(rowCriteria.get(r).getName()); + for(String parenColCriterion : parenColCriteria) { + String query = andExpressions(parenRowCriterion, parenColCriterion); + for(ColumnValueGenerator generator : valueGenerators) { + rowValues.add(generator.generateValue(nuixCase, query)); + } + } + + //TESTING + List stringRowValues = rowValues.stream().map(v -> v.toString()).collect(Collectors.toList()); + System.out.println(String.join("\t", stringRowValues)); + + sheet.appendRow(rowValues); + } + sheet.autoFitColumns(); + xlsx.save(); + } + + public List getRowCriteria() { + return rowCriteria; + } + + public void setRowCriteria(List rowCriteria) { + this.rowCriteria = rowCriteria; + } + + public void addRowCriterion(NamedQuery criterion) { + this.rowCriteria.add(criterion); + } + + public void addRowCriterion(String name, String query) { + NamedQuery nq = new NamedQuery(name,query); + addRowCriterion(nq); + } + + public void clearRowCriteria() { + this.rowCriteria.clear(); + } + + public List getColCriteria() { + return colCriteria; + } + + public void setColCriteria(List colCriteria) { + this.colCriteria = colCriteria; + } + + public void addColCriterion(NamedQuery criterion) { + this.colCriteria.add(criterion); + } + + public void addColCriterion(String name, String query) { + NamedQuery nq = new NamedQuery(name,query); + addColCriterion(nq); + } + + public List getValueGenerators() { + return valueGenerators; + } + + public void setValueGenerators(List valueGenerators) { + this.valueGenerators = valueGenerators; + } + + public void addScriptedValueGenerator(String label, BiFunction expression) { + ScriptedColumnValueGenerator scriptedGenerator = new ScriptedColumnValueGenerator(label,expression); + this.valueGenerators.add(scriptedGenerator); + } + + public String getRowCategoryLabel() { + return rowCategoryLabel; + } + + public void setRowCategoryLabel(String rowCategoryLabel) { + this.rowCategoryLabel = rowCategoryLabel; + } + + public String getColPrimaryCategoryLabel() { + return colPrimaryCategoryLabel; + } + + public void setColPrimaryCategoryLabel(String colPrimaryCategoryLabel) { + this.colPrimaryCategoryLabel = colPrimaryCategoryLabel; + } + +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java b/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java new file mode 100644 index 0000000..c00493c --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java @@ -0,0 +1,29 @@ +package com.nuix.superutilities.reporting; + +public class NamedQuery { + private String name = "Name"; + private String query = "Query"; + + public NamedQuery() {} + + public NamedQuery(String name, String query) { + this.name = name; + this.query = query; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java new file mode 100644 index 0000000..f9a292f --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java @@ -0,0 +1,19 @@ +package com.nuix.superutilities.reporting; + +import java.util.function.BiFunction; + +import nuix.Case; + +public class ScriptedColumnValueGenerator extends ColumnValueGenerator { + BiFunction expression = null; + + public ScriptedColumnValueGenerator(String label, BiFunction expression) { + this.label = label; + this.expression = expression; + } + + @Override + public Object generateValue(Case nuixCase, String query) { + return expression.apply(nuixCase,query); + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java new file mode 100644 index 0000000..3cbc111 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java @@ -0,0 +1,59 @@ +package com.nuix.superutilities.reporting; + +import java.util.List; + +import com.aspose.cells.Cell; +import com.aspose.cells.Worksheet; + +public class SimpleWorksheet { + @SuppressWarnings("unused") + private SimpleXlsx owningXlsx = null; + private Worksheet worksheet = null; + + private int currentRow = 0; + + SimpleWorksheet(SimpleXlsx owningXlsx, Worksheet worksheet){ + this.owningXlsx = owningXlsx; + this.worksheet = worksheet; + } + + public Worksheet getAsposeWorksheet() { + return worksheet; + } + + public Cell getCell(int row, int col) { + return worksheet.getCells().get(row,col); + } + + public Object getValue(int row, int col) { + return getCell(row,col).getValue(); + } + + public void setValue(int row, int col, Object value) { + getCell(row,col).setValue(value); + } + + public void mergeCols(int row, int col, int colCount) { + worksheet.getCells().merge(row, col, 1, colCount); + } + + public void autoFitColumns() throws Exception { + worksheet.autoFitColumns(); + } + + public void appendRow(List rowValues) { + for (int c = 0; c < rowValues.size(); c++) { + Object rowValue = rowValues.get(c); + setValue(currentRow,c,rowValue); + } + currentRow++; + } + + public int getCurrentRow() { + return currentRow; + } + + public void setCurrentRow(int currentRow) { + this.currentRow = currentRow; + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java new file mode 100644 index 0000000..7e90314 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java @@ -0,0 +1,71 @@ +package com.nuix.superutilities.reporting; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.aspose.cells.Style; +import com.aspose.cells.Workbook; +import com.aspose.cells.Worksheet; + +public class SimpleXlsx { + public static void initializeAspose() throws Exception { + String[] potentialAsposeLocations = new String[]{ + "com.nuix.data.util.aspose.AsposeCells", + "com.nuix.util.aspose.AsposeCells", + "com.nuix.util.AsposeCells", + }; + boolean foundAspose = false; + + for(String packageToTry : potentialAsposeLocations){ + if(foundAspose){ break; } + try { + Class clazz = Class.forName(packageToTry); + Method method = clazz.getMethod("ensureInitialised"); + method.invoke(null); + foundAspose = true; + } catch (ClassNotFoundException e) {} + } + + if(!foundAspose){ + throw new Exception("Couldn't initialize Aspose, this version of the script may not be compatible with current version of Nuix"); + } + } + + private File file = null; + private Workbook workbook = null; + @SuppressWarnings("unused") + private Map createdStyles = new HashMap(); + + public SimpleXlsx(String file) throws Exception { + initializeAspose(); + this.file = new File(file); + if(this.file.exists()) { + workbook = new Workbook(file); + } else { + workbook = new Workbook(); + workbook.getWorksheets().removeAt("Sheet1"); + } + } + + public void saveAs(String file) throws Exception { + workbook.save(file); + } + + public void save() throws Exception { + workbook.save(file.getAbsolutePath()); + } + + public SimpleWorksheet getSheet(String name) { + Worksheet worksheet = workbook.getWorksheets().get(name); + if(worksheet == null) { + worksheet = workbook.getWorksheets().add(name); + } + return new SimpleWorksheet(this,worksheet); + } + + public Workbook getAsposeWorkbook() { + return workbook; + } +} diff --git a/RubyTests/IntersectionReportTest.rb b/RubyTests/IntersectionReportTest.rb new file mode 100644 index 0000000..3398c8b --- /dev/null +++ b/RubyTests/IntersectionReportTest.rb @@ -0,0 +1,48 @@ +report_file = "D:\\Temp\\IR.xlsx" + +terms = [ + "file", "control", "list", "access", "dos", + "synchronize", "com", "nuix", "well", "read", + "system", "known", "builtin", "authority", + "group", "users", "full", "message", "net", + "hauck", "from", "fake", "org", "name", + "archive", "this", "hidden", "execute", + "version", "created", "administrators", + "authenticated", "only", "part", "modified", + "type", "accessed", "date", "content", +] + +custodians = $current_case.getAllCustodians + +script_directory = File.dirname(__FILE__) +require File.join(script_directory,"SuperUtilities.jar") +java_import com.nuix.superutilities.SuperUtilities +java_import com.nuix.superutilities.reporting.IntersectionReport +$su = SuperUtilities.init($utilities,NUIX_VERSION) + +report = IntersectionReport.new(report_file) + +terms.each do |term| + report.addRowCriterion(term.capitalize,term) +end + +custodians.each do |custodian| + report.addColCriterion(custodian, "custodian:\"#{custodian}\"") +end + +report.addScriptedValueGenerator("Emails") do |nuixCase,query| + extended_query = "(#{query}) AND kind:email AND has-exclusion:0" + next nuixCase.count(extended_query) +end + +report.addScriptedValueGenerator("Email Families") do |nuixCase,query| + extended_query = "(#{query}) AND kind:email AND has-exclusion:0" + items = nuixCase.search(extended_query) + items = $utilities.getItemUtility.findFamilies(items) + items = items.reject{|i|i.isExcluded} + next items.size +end + +report.setRowCategoryLabel("Terms") +report.setColPrimaryCategoryLabel("Custodians") +report.generate($current_case,"TestSheet1") \ No newline at end of file From 58befca09d2e1adeb7fbcc33c0de53a28daaf7c5 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Thu, 3 Jan 2019 10:56:11 -0800 Subject: [PATCH 2/6] Continued development --- .../reporting/AsposeCellsColorHelper.java | 69 ++++++ .../reporting/AsposeCellsStyleHelper.java | 35 +++ .../superutilities/reporting/ColorRing.java | 60 +++++ .../reporting/ColumnValueGenerator.java | 15 ++ .../reporting/IntersectionReport.java | 220 ++++++++++-------- .../IntersectionReportSheetConfiguration.java | 166 +++++++++++++ .../superutilities/reporting/NamedQuery.java | 21 ++ .../ScriptedColumnValueGenerator.java | 10 + .../reporting/SimpleWorksheet.java | 78 +++++++ .../superutilities/reporting/SimpleXlsx.java | 16 +- RubyTests/IntersectionReportTest.rb | 36 ++- 11 files changed, 625 insertions(+), 101 deletions(-) create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsStyleHelper.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java new file mode 100644 index 0000000..ed4e969 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java @@ -0,0 +1,69 @@ +package com.nuix.superutilities.reporting; + +import com.aspose.cells.Color; + +/*** + * A class containing helper methods for working with Aspose Cells colors. + * @author Jason Wells + * + */ +public class AsposeCellsColorHelper { + /*** + * Tints a particular color channel value by a certain degree. + * @param colorChannelValue Color channel value (0-255) to tint + * @param degree The degree in which to tint the color channel. + * @return A tinted version of the color channel value + */ + public static int tintChannel(int colorChannelValue, float degree){ + if(degree == 0) + return colorChannelValue; + + int tint = (int) (colorChannelValue + (0.25 * degree * (255 - colorChannelValue))); + if(tint < 0) + return 0; + else if(tint > 255) + return 255; + else + return tint; + } + + /*** + * Tints a color by the given degree + * @param red Red color channel value (0-255) + * @param green Green color channel value (0-255) + * @param blue Blue color channel value (0-255) + * @param degree Degree to which all the color channels will be tinted + * @return A new Color object which has been tinted the specified amount + */ + public static Color getTint(int red, int green, int blue, float degree){ + return Color.fromArgb( + tintChannel(red,degree), + tintChannel(green,degree), + tintChannel(blue,degree) + ); + } + + /*** + * Helper method to convert signed byte to unsigned int + * @param b The input byte value + * @return Unsigned int equivalent + */ + private static int byteToUnsignedInt(byte b) { + return b & 0xFF; + } + + /*** + * + * @param baseColor + * @param degree + * @return + */ + public static Color getTint(Color baseColor, float degree) { + return getTint( + byteToUnsignedInt(baseColor.getR()), + byteToUnsignedInt(baseColor.getG()), + byteToUnsignedInt(baseColor.getB()), + degree + ); + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsStyleHelper.java b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsStyleHelper.java new file mode 100644 index 0000000..0319e81 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsStyleHelper.java @@ -0,0 +1,35 @@ +package com.nuix.superutilities.reporting; + +import java.util.ArrayList; +import java.util.List; + +import com.aspose.cells.Border; +import com.aspose.cells.BorderCollection; +import com.aspose.cells.BorderType; +import com.aspose.cells.CellBorderType; +import com.aspose.cells.Color; +import com.aspose.cells.Style; + +/*** + * A class containing helper methods for working with Aspose Cells Styles. + * @author Jason Wells + * + */ +public class AsposeCellsStyleHelper { + /*** + * Convenience method for applying a thin black border to all sides of a given cell style. + * @param style The style to modify the borders of. + */ + public static void enableAllBorders(Style style) { + BorderCollection bordersCollection = style.getBorders(); + List borders = new ArrayList(); + borders.add(bordersCollection.getByBorderType(BorderType.LEFT_BORDER)); + borders.add(bordersCollection.getByBorderType(BorderType.TOP_BORDER)); + borders.add(bordersCollection.getByBorderType(BorderType.RIGHT_BORDER)); + borders.add(bordersCollection.getByBorderType(BorderType.BOTTOM_BORDER)); + for(Border border : borders) { + border.setColor(Color.getBlack()); + border.setLineStyle(CellBorderType.THIN); + } + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java b/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java new file mode 100644 index 0000000..366d431 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java @@ -0,0 +1,60 @@ +package com.nuix.superutilities.reporting; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.aspose.cells.Color; + +/*** + * Iterator over a collection of Aspose Cells Color objects. When the end of the collection is hit it + * automatically starts at the beginning again, effectively creating an infinite circular collection. + * @author Jason Wells + * + */ +public class ColorRing implements Iterator { + + private List colors = new ArrayList(); + int pos = 0; + + @Override + public boolean hasNext() { + return colors.size() > 0; + } + + @Override + public Color next() { + Color nextColor = colors.get(pos); + pos++; + if(pos > colors.size() - 1) { + pos = 0; + } + return nextColor; + } + + /*** + * Adds the provided base color and series of tinted variations to this instance. + * @param baseColor The based color to start with + * @param tintSteps The number of additional tinted versions of the base color to add + */ + public void addTintSeries(Color baseColor, int tintSteps) { + for (int i = 0; i <= tintSteps; i++) { + colors.add(AsposeCellsColorHelper.getTint(baseColor, (float)i)); + } + } + + /*** + * Clears all colors currently assigned to this instance. + */ + public void clearColors() { + colors.clear(); + } + + /*** + * Moves position of this collection back to the start so that next call to {@link #next()} will return + * the first color in the sequence. + */ + public void restart() { + pos = 0; + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java index da7f0b7..24525c4 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java @@ -2,13 +2,28 @@ import nuix.Case; +/*** + * Base class for reporting. You should not use this class directly, but instead use classes + * derived from it. + * @author Jason Wells + * + */ public class ColumnValueGenerator { protected String label = "Value"; public Object generateValue(Case nuixCase, String query) { return "Not Implemented"; } + /*** + * Gets the label associated to this instance. + * @return The associated label + */ public String getLabel() { return label; } + + /*** + * Sets the label associated to this instance + * @param label The label to associate + */ public void setLabel(String label) { this.label = label; } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java index 5131580..98743bf 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java @@ -2,147 +2,185 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.BiFunction; +import java.util.StringJoiner; import java.util.stream.Collectors; +import org.apache.log4j.Logger; + +import com.aspose.cells.BackgroundType; +import com.aspose.cells.Color; +import com.aspose.cells.Style; + import nuix.Case; +/*** + * This class generates an "intersection report". The report is generated using 3 things: row criteria, column criteria and column value generators. + * Row and column criteria are collections of {@link NamedQuery} objects. The value reported in a given cell is calculated by first AND'ing together + * the relevant row and column criterion. In turn the AND'ed expression is provided to a {@link ColumnValueGenerator} which can make use of the query + * provided to calculate its particular value. Examples might be running Case.count(query) to get a responsive item count or running Case.search(query) + * and tweaking the results to include families or calling methods on CaseStatistics such as getAuditSize(query) to report responsive items total audited size. + * This class takes care of iteratively running these things and providing formatting as they are written into an XLSX Excel spreadsheet. If a scope query + * is provided via the method {@link #setScopeQuery(String)}, the provided query will be AND'ed together as well with each row/col criteria combination, allowing + * you to further scope the overall report to things like particular evidence, item types, tags, etc. + * @author Jason Wells + * + */ public class IntersectionReport { - private List rowCriteria = new ArrayList(); - private List colCriteria = new ArrayList(); - private List valueGenerators = new ArrayList(); - - private String rowCategoryLabel = "Term"; - private String colPrimaryCategoryLabel = "Column Category"; + private static Logger logger = Logger.getLogger(IntersectionReport.class); + + private SimpleXlsx xlsx = null; + private Style rowCategoryLabelStyle = null; + private Style colPrimaryCategoryLabelStyle = null; + private Style rowCategoryStyle = null; + private Style colCategoryStyle = null; + private Style rowGeneralStyle = null; + + private ColorRing colCategoryColorRing = new ColorRing(); + + /*** + * Takes provided expression string and returns that expression wrapped in parens + * @param expression The expression to wrap in parens + * @return The expression wrapped in parens + */ private String parenExpression(String expression) { return "("+expression+")"; } + /*** + * Joins a list of expressions into a single AND'ed expression + * @param expressions The expressions to AND together + * @return A single query expression of the provided expressions AND'ed together + */ private String andExpressions(String... expressions) { - return String.join(" AND ", expressions); + StringJoiner result = new StringJoiner(" AND "); + for (int i = 0; i < expressions.length; i++) { + String expression = expressions[i]; + if(expression == null || expression.isEmpty()) { + // Don't want to include empty or null expressions + continue; + } + else if(expression.contentEquals("()")) { + // Strip out empty expressions that may have passed throuh parenExpression method + continue; + } + else { + result.add(expression); + } + } + return result.toString(); } + /*** + * Creates a new instance + * @param file File path of existing XLSX if you wish to add to an existing workbook, can be null + * @throws Exception Thrown if there are errors + */ public IntersectionReport(String file) throws Exception { xlsx = new SimpleXlsx(file); + + rowCategoryLabelStyle = xlsx.createStyle(); + rowCategoryLabelStyle.getFont().setBold(true); + rowCategoryLabelStyle.getFont().setSize(14); + AsposeCellsStyleHelper.enableAllBorders(rowCategoryLabelStyle); + + colPrimaryCategoryLabelStyle = xlsx.createStyle(); + colPrimaryCategoryLabelStyle.getFont().setBold(true); + colPrimaryCategoryLabelStyle.getFont().setSize(14); + AsposeCellsStyleHelper.enableAllBorders(colPrimaryCategoryLabelStyle); + + rowCategoryStyle = xlsx.createStyle(); + rowCategoryStyle.getFont().setBold(true); + rowCategoryStyle.setPattern(BackgroundType.SOLID); + AsposeCellsStyleHelper.enableAllBorders(rowCategoryStyle); + + colCategoryStyle = xlsx.createStyle(); + colCategoryStyle.getFont().setBold(true); + colCategoryStyle.setPattern(BackgroundType.SOLID); + AsposeCellsStyleHelper.enableAllBorders(colCategoryStyle); + + colCategoryColorRing.addTintSeries(Color.fromArgb(255, 51, 51), 4); // Red + colCategoryColorRing.addTintSeries(Color.fromArgb(51, 204, 51), 4); // Green + colCategoryColorRing.addTintSeries(Color.fromArgb(0, 153, 204), 4); // Blue + + rowGeneralStyle = xlsx.createStyle(); + AsposeCellsStyleHelper.enableAllBorders(rowGeneralStyle); } - public void generate(Case nuixCase, String sheetName) throws Exception { + /*** + * Generates a new report sheet using the row criteria, column criteria and value generators currently assigned to this instance + * @param nuixCase The Nuix case report data is collected against + * @param sheetName The name of the excel worksheet to create + * @throws Exception Thrown if there are errors + */ + public void generate(Case nuixCase, String sheetName, IntersectionReportSheetConfiguration sheetConfig) throws Exception { SimpleWorksheet sheet = xlsx.getSheet(sheetName); - List parenRowCriteria = rowCriteria.stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); - List parenColCriteria = colCriteria.stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); + List parenRowCriteria = sheetConfig.getRowCriteria().stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); + List parenColCriteria = sheetConfig.getColCriteria().stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); List rowValues = new ArrayList(); + String parenScopeQuery = parenExpression(sheetConfig.getScopeQuery()); // Start out by building out headers - sheet.setValue(0, 0, colPrimaryCategoryLabel); - sheet.setValue(1, 0, rowCategoryLabel); - for (int c = 0; c < colCriteria.size(); c++) { + sheet.setValue(0, 0, sheetConfig.getColPrimaryCategoryLabel()); + sheet.setStyle(0, 0, colPrimaryCategoryLabelStyle); + + sheet.setValue(1, 0, sheetConfig.getRowCategoryLabel()); + sheet.setStyle(1, 0, rowCategoryLabelStyle); + + for (int c = 0; c < sheetConfig.getColCriteria().size(); c++) { // First Row - String colCriterion = colCriteria.get(c).getName(); - int col = 1 + (c * valueGenerators.size()); + Style colCategoryStyleCopy = xlsx.createStyle(); + colCategoryStyleCopy.copy(colCategoryStyle); + colCategoryStyleCopy.setForegroundColor(colCategoryColorRing.next()); + + String colCriterion = sheetConfig.getColCriteria().get(c).getName(); + int col = 1 + (c * sheetConfig.getValueGenerators().size()); sheet.setValue(0, col, colCriterion); - sheet.mergeCols(0, col, valueGenerators.size()); + + sheet.setStyle(0, col, colCategoryStyleCopy); + sheet.mergeCols(0, col, sheetConfig.getValueGenerators().size()); // Second Row - for (int sc = 0; sc < valueGenerators.size(); sc++) { + for (int sc = 0; sc < sheetConfig.getValueGenerators().size(); sc++) { int subCol = col + sc; - sheet.setValue(1, subCol, valueGenerators.get(sc).getLabel()); + sheet.setValue(1, subCol, sheetConfig.getValueGenerators().get(sc).getLabel()); + sheet.setStyle(1, subCol, colCategoryStyle); } } sheet.setCurrentRow(sheet.getCurrentRow()+2); - for (int r = 0; r < rowCriteria.size(); r++) { + for (int r = 0; r < sheetConfig.getRowCriteria().size(); r++) { String parenRowCriterion = parenRowCriteria.get(r); rowValues.clear(); - rowValues.add(rowCriteria.get(r).getName()); + rowValues.add(sheetConfig.getRowCriteria().get(r).getName()); + for(String parenColCriterion : parenColCriteria) { - String query = andExpressions(parenRowCriterion, parenColCriterion); - for(ColumnValueGenerator generator : valueGenerators) { + String query = andExpressions(parenScopeQuery, parenRowCriterion, parenColCriterion); + logger.info(String.format("Query: %s", query)); + for(ColumnValueGenerator generator : sheetConfig.getValueGenerators()) { rowValues.add(generator.generateValue(nuixCase, query)); } } //TESTING - List stringRowValues = rowValues.stream().map(v -> v.toString()).collect(Collectors.toList()); - System.out.println(String.join("\t", stringRowValues)); +// List stringRowValues = rowValues.stream().map(v -> v.toString()).collect(Collectors.toList()); +// logger.info(String.join("\t", stringRowValues)); + + sheet.appendRow(rowValues, rowGeneralStyle); - sheet.appendRow(rowValues); + // Apply styles + sheet.setStyle(sheet.getCurrentRow()-1, 0, rowCategoryStyle); } sheet.autoFitColumns(); xlsx.save(); } - public List getRowCriteria() { - return rowCriteria; - } - - public void setRowCriteria(List rowCriteria) { - this.rowCriteria = rowCriteria; - } - public void addRowCriterion(NamedQuery criterion) { - this.rowCriteria.add(criterion); - } - - public void addRowCriterion(String name, String query) { - NamedQuery nq = new NamedQuery(name,query); - addRowCriterion(nq); - } - - public void clearRowCriteria() { - this.rowCriteria.clear(); - } - - public List getColCriteria() { - return colCriteria; - } - - public void setColCriteria(List colCriteria) { - this.colCriteria = colCriteria; - } - - public void addColCriterion(NamedQuery criterion) { - this.colCriteria.add(criterion); - } - - public void addColCriterion(String name, String query) { - NamedQuery nq = new NamedQuery(name,query); - addColCriterion(nq); - } - - public List getValueGenerators() { - return valueGenerators; - } - - public void setValueGenerators(List valueGenerators) { - this.valueGenerators = valueGenerators; - } - - public void addScriptedValueGenerator(String label, BiFunction expression) { - ScriptedColumnValueGenerator scriptedGenerator = new ScriptedColumnValueGenerator(label,expression); - this.valueGenerators.add(scriptedGenerator); - } - - public String getRowCategoryLabel() { - return rowCategoryLabel; - } - - public void setRowCategoryLabel(String rowCategoryLabel) { - this.rowCategoryLabel = rowCategoryLabel; - } - - public String getColPrimaryCategoryLabel() { - return colPrimaryCategoryLabel; - } - - public void setColPrimaryCategoryLabel(String colPrimaryCategoryLabel) { - this.colPrimaryCategoryLabel = colPrimaryCategoryLabel; - } } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java new file mode 100644 index 0000000..1f3c30e --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java @@ -0,0 +1,166 @@ +package com.nuix.superutilities.reporting; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import nuix.Case; + +public class IntersectionReportSheetConfiguration { + private List rowCriteria = new ArrayList(); + private List colCriteria = new ArrayList(); + private List valueGenerators = new ArrayList(); + + private String rowCategoryLabel = "Term"; + private String colPrimaryCategoryLabel = "Column Category"; + private String scopeQuery = ""; + + /*** + * Gets the list of criteria used for each row. + * @return List of row criteria. + */ + public List getRowCriteria() { + return rowCriteria; + } + + /*** + * Sets the list of criteria used for each row. + * @param rowCriteria The list of criteria which defines each row. + */ + public void setRowCriteria(List rowCriteria) { + this.rowCriteria = rowCriteria; + } + + /*** + * Adds a single {@link NamedQuery} to the list of row criteria. + * @param criterion The named query to add to the list of row criteria. + */ + public void addRowCriterion(NamedQuery criterion) { + this.rowCriteria.add(criterion); + } + + /*** + * Adds a single {@link NamedQuery}, constructed from the provided arguments, to the list of row criteria. + * @param name Name value used when constructing the {@link NamedQuery} object. + * @param query Query value used when constructing the {@link NamedQuery} object. + */ + public void addRowCriterion(String name, String query) { + NamedQuery nq = new NamedQuery(name,query); + addRowCriterion(nq); + } + + /*** + * Removes all currently assigned row criteria. + */ + public void clearRowCriteria() { + this.rowCriteria.clear(); + } + + /*** + * Gets the list of criteria used for each primary column category. + * @return The list of criteria used for each primary column category. + */ + public List getColCriteria() { + return colCriteria; + } + + /*** + * Sets the list of criteria used for each primary column category. + * @param colCriteria The list of criteria to use for each primary column category. + */ + public void setColCriteria(List colCriteria) { + this.colCriteria = colCriteria; + } + + /*** + * Adds a single {@link NamedQuery} to the list of primary column criteria. + * @param criterion The named query to add to the list of primary column criteria. + */ + public void addColCriterion(NamedQuery criterion) { + this.colCriteria.add(criterion); + } + + /*** + * Adds a single {@link NamedQuery}, constructed from the provided arguments, to the list of primary column criteria. + * @param name Name value used when constructing the {@link NamedQuery} object. + * @param query Query value used when constructing the {@link NamedQuery} object. + */ + public void addColCriterion(String name, String query) { + NamedQuery nq = new NamedQuery(name,query); + addColCriterion(nq); + } + + /*** + * Gets the list of {@link ColumnValueGenerator} objects used to calculate the value of each secondary column nested beneath any given primary column. + * @return The list of {@link ColumnValueGenerator} objects used to calculate the value of each secondary column nested beneath any given primary column. + */ + public List getValueGenerators() { + return valueGenerators; + } + + /*** + * Sets the list of {@link ColumnValueGenerator} objects used to calculate the value of each secondary column nested beneath any given primary column. + * @param valueGenerators The list of {@link ColumnValueGenerator} objects used to calculate the value of each secondary column nested beneath any given primary column. + */ + public void setValueGenerators(List valueGenerators) { + this.valueGenerators = valueGenerators; + } + + /*** + * Adds a {@link ColumnValueGenerator} which calculates its value using the expression provided. + * @param label The label used for this secondary column + * @param expression The expression used to calculate this secondary column's value. Expression is provided a Nuix Case object and query and should return an object such as a String or integer value. + */ + public void addScriptedValueGenerator(String label, BiFunction expression) { + ScriptedColumnValueGenerator scriptedGenerator = new ScriptedColumnValueGenerator(label,expression); + this.valueGenerators.add(scriptedGenerator); + } + + /*** + * Gets the overall row category label. + * @return The overall row category label. + */ + public String getRowCategoryLabel() { + return rowCategoryLabel; + } + + /*** + * Sets the overall row category label. For example if each row is a search term, then you might set the label to "Terms". + * @param rowCategoryLabel The overall row category label. + */ + public void setRowCategoryLabel(String rowCategoryLabel) { + this.rowCategoryLabel = rowCategoryLabel; + } + + /*** + * Gets the overall primary column category label. + * @return The overall primary column category label. + */ + public String getColPrimaryCategoryLabel() { + return colPrimaryCategoryLabel; + } + + /*** + * Sets the overall primary column category label. For example, if each primary column is a custodian name, then you might set the label to "Custodians". + * @param colPrimaryCategoryLabel The overall primary column label. + */ + public void setColPrimaryCategoryLabel(String colPrimaryCategoryLabel) { + this.colPrimaryCategoryLabel = colPrimaryCategoryLabel; + } + + /*** + * Gets the current scope query which scopes the overall item set reported on. A blank value is equivalent to no overall scope. + * @return The current overall scope query. + */ + public String getScopeQuery() { + return scopeQuery; + } + + /*** + * Sets the current scope query which scopes the overall item set reported on. A blank value is equivalent to no overall scope. + * @param scopeQuery The overall scope query to use. + */ + public void setScopeQuery(String scopeQuery) { + this.scopeQuery = scopeQuery; + } +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java b/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java index c00493c..322f4a3 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/NamedQuery.java @@ -1,5 +1,10 @@ package com.nuix.superutilities.reporting; +/*** + * Encapsulates a Nuix query string and an associated name. + * @author Jason Wells + * + */ public class NamedQuery { private String name = "Name"; private String query = "Query"; @@ -11,18 +16,34 @@ public NamedQuery(String name, String query) { this.query = query; } + /*** + * Gets the associated name + * @return The associated name + */ public String getName() { return name; } + /*** + * Sets the associated name + * @param name The name to associate + */ public void setName(String name) { this.name = name; } + /*** + * Gets the associated query + * @return The associated query + */ public String getQuery() { return query; } + /*** + * Sets the associated query + * @param query The query to associate + */ public void setQuery(String query) { this.query = query; } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java index f9a292f..cae3dd7 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java @@ -4,9 +4,19 @@ import nuix.Case; +/*** + * A {@link ColumnValueGenerator} which uses the provided BiFunction expression to calculate its value. + * @author Jason Wells + * + */ public class ScriptedColumnValueGenerator extends ColumnValueGenerator { BiFunction expression = null; + /*** + * Creates a new instance which will used the specified expression. + * @param label The label to use when generating a report + * @param expression The expression used to calculate this column's value. Expression will be provided a Nuix case object and query String and is expected to return a value such as a string or integer. + */ public ScriptedColumnValueGenerator(String label, BiFunction expression) { this.label = label; this.expression = expression; diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java index 3cbc111..d10c7f6 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java @@ -1,10 +1,18 @@ package com.nuix.superutilities.reporting; import java.util.List; +import java.util.function.Consumer; import com.aspose.cells.Cell; +import com.aspose.cells.Style; import com.aspose.cells.Worksheet; +/*** + * Wrapper class around Aspose Cells Worksheet object which simplifies some of the + * more common operations. + * @author Jason Wells + * + */ public class SimpleWorksheet { @SuppressWarnings("unused") private SimpleXlsx owningXlsx = null; @@ -12,27 +20,80 @@ public class SimpleWorksheet { private int currentRow = 0; + /*** + * Creates a new instance. + * @param owningXlsx The {@link SimpleXlsx} this sheet is associated with. + * @param worksheet The Aspose Cells Worksheet object this is wrapping around. + */ SimpleWorksheet(SimpleXlsx owningXlsx, Worksheet worksheet){ this.owningXlsx = owningXlsx; this.worksheet = worksheet; } + /*** + * Gets the underlying Aspose Cells Worksheet object. + * @return The underlying Aspose Cells Worksheet object. + */ public Worksheet getAsposeWorksheet() { return worksheet; } + /*** + * Gets a particular Cell object from this work sheet. + * @param row The 0 based row index + * @param col The 0 based column index + * @return The underlying Aspose Cells Cell object at the given indices + */ public Cell getCell(int row, int col) { return worksheet.getCells().get(row,col); } + /*** + * Gets the value of a particular cell in this work sheet + * @param row The 0 based row index + * @param col The 0 based column index + * @return The value of the cell at the given indices + */ public Object getValue(int row, int col) { return getCell(row,col).getValue(); } + /*** + * Sets the value of a particular cell in this work sheet + * @param row The 0 based row index + * @param col The 0 based column index + * @param value The value to assign to the cell at the given indices + */ public void setValue(int row, int col, Object value) { getCell(row,col).setValue(value); } + /*** + * Sets the style of of a particular cell in this work sheet + * @param row The 0 based row index + * @param col The 0 based column index + * @param style The Style to apply to the cell at the given indices + */ + public void setStyle(int row, int col, Style style) { + getCell(row, col).setStyle(style); + } + + /*** + * Gets the style of a particular cell in this work sheet + * @param row The 0 based row index + * @param col The 0 based column index + * @return The Style of the cell at the given indices + */ + public Style getStyle(int row, int col) { + return getCell(row,col).getStyle(); + } + + /*** + * Merges a series of cells horizontally + * @param row The 0 based row index + * @param col The 0 based column index of the first column in the merged group of cells. + * @param colCount How many cells total to merge, so if you want the 2 cells following the first cell specified by the indices, provide a value of 3. + */ public void mergeCols(int row, int col, int colCount) { worksheet.getCells().merge(row, col, 1, colCount); } @@ -48,6 +109,23 @@ public void appendRow(List rowValues) { } currentRow++; } + + public void appendRow(List rowValues, Style defaultStyle) { + for (int c = 0; c < rowValues.size(); c++) { + Object rowValue = rowValues.get(c); + setValue(currentRow,c,rowValue); + setStyle(currentRow,c,defaultStyle); + } + currentRow++; + } + + public void eachCell(int startRow, int endRow, int startCol, int endCol, Consumer activity) { + for (int r = startRow; r <= endRow; r++) { + for (int c = startCol; c <= endCol; c++) { + activity.accept(getCell(r,c)); + } + } + } public int getCurrentRow() { return currentRow; diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java index 7e90314..e8cfb94 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleXlsx.java @@ -40,8 +40,8 @@ public static void initializeAspose() throws Exception { public SimpleXlsx(String file) throws Exception { initializeAspose(); - this.file = new File(file); - if(this.file.exists()) { + if(file != null) { this.file = new File(file); } + if(this.file != null && this.file.exists()) { workbook = new Workbook(file); } else { workbook = new Workbook(); @@ -50,10 +50,16 @@ public SimpleXlsx(String file) throws Exception { } public void saveAs(String file) throws Exception { + if(file == null || file.trim().isEmpty()) { + throw new IllegalArgumentException("Empyt/null value for 'file' argument, save(String file) method doesn't know where to save the file to"); + } workbook.save(file); } public void save() throws Exception { + if(this.file == null) { + throw new IllegalArgumentException("Instance has no value for 'file', save() method doesn't know where to save the file to"); + } workbook.save(file.getAbsolutePath()); } @@ -68,4 +74,10 @@ public SimpleWorksheet getSheet(String name) { public Workbook getAsposeWorkbook() { return workbook; } + + public Style createStyle() { + Style newStyle = workbook.createStyle(); + workbook.getNamedStyle("test"); + return newStyle; + } } diff --git a/RubyTests/IntersectionReportTest.rb b/RubyTests/IntersectionReportTest.rb index 3398c8b..2015a80 100644 --- a/RubyTests/IntersectionReportTest.rb +++ b/RubyTests/IntersectionReportTest.rb @@ -1,4 +1,4 @@ -report_file = "D:\\Temp\\IR.xlsx" +report_file = "D:\\Temp\\IR_#{Time.now.to_i}.xlsx" terms = [ "file", "control", "list", "access", "dos", @@ -12,30 +12,45 @@ "type", "accessed", "date", "content", ] +opened_case = false +if $current_case.nil? + puts "Opening case..." + $current_case = $utilities.getCaseFactory.open('D:\cases\FakeDataCompound') + opened_case = true +end + +scope_query = "" + custodians = $current_case.getAllCustodians script_directory = File.dirname(__FILE__) require File.join(script_directory,"SuperUtilities.jar") java_import com.nuix.superutilities.SuperUtilities java_import com.nuix.superutilities.reporting.IntersectionReport +java_import com.nuix.superutilities.reporting.IntersectionReportSheetConfiguration $su = SuperUtilities.init($utilities,NUIX_VERSION) report = IntersectionReport.new(report_file) +sheet_config = IntersectionReportSheetConfiguration.new + +sheet_config.setScopeQuery(scope_query) terms.each do |term| - report.addRowCriterion(term.capitalize,term) + puts "Adding Term: #{term}" + sheet_config.addRowCriterion(term.capitalize,term) end custodians.each do |custodian| - report.addColCriterion(custodian, "custodian:\"#{custodian}\"") + puts "Adding Custodian: #{custodian}" + sheet_config.addColCriterion(custodian, "custodian:\"#{custodian}\"") end -report.addScriptedValueGenerator("Emails") do |nuixCase,query| +sheet_config.addScriptedValueGenerator("Emails") do |nuixCase,query| extended_query = "(#{query}) AND kind:email AND has-exclusion:0" next nuixCase.count(extended_query) end -report.addScriptedValueGenerator("Email Families") do |nuixCase,query| +sheet_config.addScriptedValueGenerator("Email Families") do |nuixCase,query| extended_query = "(#{query}) AND kind:email AND has-exclusion:0" items = nuixCase.search(extended_query) items = $utilities.getItemUtility.findFamilies(items) @@ -43,6 +58,11 @@ next items.size end -report.setRowCategoryLabel("Terms") -report.setColPrimaryCategoryLabel("Custodians") -report.generate($current_case,"TestSheet1") \ No newline at end of file +sheet_config.setRowCategoryLabel("Terms") +sheet_config.setColPrimaryCategoryLabel("Custodians") +report.generate($current_case,"TestSheet1",sheet_config) + +if opened_case + puts "Closing case opened by script..." + $current_case.close +end \ No newline at end of file From f17715ea798b5f10b5ebd5118c9eef73dbc9469f Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Mon, 7 Jan 2019 17:02:29 -0800 Subject: [PATCH 3/6] Category color ring restarted each time a new sheet is generated --- .../com/nuix/superutilities/reporting/IntersectionReport.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java index 98743bf..850547c 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java @@ -29,8 +29,6 @@ public class IntersectionReport { private static Logger logger = Logger.getLogger(IntersectionReport.class); - - private SimpleXlsx xlsx = null; private Style rowCategoryLabelStyle = null; @@ -117,6 +115,7 @@ public IntersectionReport(String file) throws Exception { * @throws Exception Thrown if there are errors */ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetConfiguration sheetConfig) throws Exception { + colCategoryColorRing.restart(); SimpleWorksheet sheet = xlsx.getSheet(sheetName); List parenRowCriteria = sheetConfig.getRowCriteria().stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); From 11616390a328a4da1143ae759bd1a8cffdc8c327 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Tue, 8 Jan 2019 16:54:46 -0800 Subject: [PATCH 4/6] Various tweaks due to testing IntersectionReport --- .../reporting/AsposeCellsColorHelper.java | 2 +- .../superutilities/reporting/ColorRing.java | 4 + .../reporting/ColumnValueGenerator.java | 10 +++ .../reporting/IntersectionReport.java | 79 +++++++++++++++++-- .../IntersectionReportProgressCallback.java | 5 ++ .../ScriptedColumnValueGenerator.java | 6 ++ .../reporting/SimpleWorksheet.java | 4 + 7 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportProgressCallback.java diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java index ed4e969..3a3ff02 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/AsposeCellsColorHelper.java @@ -18,7 +18,7 @@ public static int tintChannel(int colorChannelValue, float degree){ if(degree == 0) return colorChannelValue; - int tint = (int) (colorChannelValue + (0.25 * degree * (255 - colorChannelValue))); + int tint = (int) (colorChannelValue + (degree * (255f - (float)colorChannelValue))); if(tint < 0) return 0; else if(tint > 255) diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java b/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java index 366d431..6b796ab 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ColorRing.java @@ -57,4 +57,8 @@ public void clearColors() { public void restart() { pos = 0; } + + public void addColor(int red, int green, int blue) { + colors.add(Color.fromArgb(red, green, blue)); + } } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java index 24525c4..affcba1 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ColumnValueGenerator.java @@ -10,6 +10,7 @@ */ public class ColumnValueGenerator { protected String label = "Value"; + protected String columnLabel = null; public Object generateValue(Case nuixCase, String query) { return "Not Implemented"; } /*** @@ -20,6 +21,11 @@ public String getLabel() { return label; } + public String getColumnLabel() { + if(columnLabel == null) { return label; } + else { return columnLabel; } + } + /*** * Sets the label associated to this instance * @param label The label to associate @@ -27,4 +33,8 @@ public String getLabel() { public void setLabel(String label) { this.label = label; } + + public void setColumnLabel(String categoryLabel) { + this.columnLabel = categoryLabel; + } } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java index 850547c..dc0694e 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.log4j.Logger; @@ -39,6 +40,30 @@ public class IntersectionReport { private ColorRing colCategoryColorRing = new ColorRing(); + private IntersectionReportProgressCallback progressCallback = null; + + private void fireProgress(int current, int total) { + if(progressCallback != null) { + progressCallback.progressUpdated(current, total); + } + } + + public void whenProgressUpdated(IntersectionReportProgressCallback callback) { + progressCallback = callback; + } + + private Consumer messageCallback = null; + + private void fireMessage(String message) { + if(messageCallback != null) { + messageCallback.accept(message); + } + } + + public void whenMessageGenerated(Consumer callback) { + messageCallback = callback; + } + /*** * Takes provided expression string and returns that expression wrapped in parens * @param expression The expression to wrap in parens @@ -100,6 +125,8 @@ public IntersectionReport(String file) throws Exception { colCategoryStyle.setPattern(BackgroundType.SOLID); AsposeCellsStyleHelper.enableAllBorders(colCategoryStyle); + // These are intended as generic defaults and you should really call getColCategoryColorRing + // and configure the colors if they want them to look at all nice colCategoryColorRing.addTintSeries(Color.fromArgb(255, 51, 51), 4); // Red colCategoryColorRing.addTintSeries(Color.fromArgb(51, 204, 51), 4); // Green colCategoryColorRing.addTintSeries(Color.fromArgb(0, 153, 204), 4); // Blue @@ -121,10 +148,15 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon List parenRowCriteria = sheetConfig.getRowCriteria().stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); List parenColCriteria = sheetConfig.getColCriteria().stream().map(c -> parenExpression(c.getQuery())).collect(Collectors.toList()); + int currentCellIndex = 0; + int totalCells = parenRowCriteria.size() * parenColCriteria.size() * sheetConfig.getValueGenerators().size(); + long lastProgress = System.currentTimeMillis(); + List rowValues = new ArrayList(); String parenScopeQuery = parenExpression(sheetConfig.getScopeQuery()); // Start out by building out headers + fireMessage("Building headers..."); sheet.setValue(0, 0, sheetConfig.getColPrimaryCategoryLabel()); sheet.setStyle(0, 0, colPrimaryCategoryLabelStyle); @@ -135,7 +167,8 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon // First Row Style colCategoryStyleCopy = xlsx.createStyle(); colCategoryStyleCopy.copy(colCategoryStyle); - colCategoryStyleCopy.setForegroundColor(colCategoryColorRing.next()); + Color primaryCategoryColor = colCategoryColorRing.next(); + colCategoryStyleCopy.setForegroundColor(primaryCategoryColor); String colCriterion = sheetConfig.getColCriteria().get(c).getName(); int col = 1 + (c * sheetConfig.getValueGenerators().size()); @@ -147,12 +180,31 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon // Second Row for (int sc = 0; sc < sheetConfig.getValueGenerators().size(); sc++) { int subCol = col + sc; - sheet.setValue(1, subCol, sheetConfig.getValueGenerators().get(sc).getLabel()); - sheet.setStyle(1, subCol, colCategoryStyle); + + // Secondary column headers are tinted colors of primary column header color + Style colSecondaryCategoryStyleCopy = xlsx.createStyle(); + colSecondaryCategoryStyleCopy.copy(colCategoryStyle); + colSecondaryCategoryStyleCopy.setTextWrapped(true); + + // We want to tint the primary color to some degree, 1.0 tends to blow out the color to white + // so we will tint it relative. For example if we have 4 columns we want roughly the first tinted 25% + // then the next 50%, then 75% and finally 100%. We subtract a little so that we never hit + // 100% tint. + float tintDegree = ((float)sc+1) / ((float)sheetConfig.getValueGenerators().size()) - 0.15f; + + Color primaryCategoryColorTint = AsposeCellsColorHelper.getTint(primaryCategoryColor, tintDegree); + colSecondaryCategoryStyleCopy.setForegroundColor(primaryCategoryColorTint); + + String secondaryColumnLabel = sheetConfig.getValueGenerators().get(sc).getColumnLabel(); + sheet.setValue(1, subCol, secondaryColumnLabel); + sheet.setStyle(1, subCol, colSecondaryCategoryStyleCopy); } } + + sheet.autoFitRow(2); sheet.setCurrentRow(sheet.getCurrentRow()+2); + fireMessage("Building rows..."); for (int r = 0; r < sheetConfig.getRowCriteria().size(); r++) { String parenRowCriterion = parenRowCriteria.get(r); @@ -164,6 +216,13 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon logger.info(String.format("Query: %s", query)); for(ColumnValueGenerator generator : sheetConfig.getValueGenerators()) { rowValues.add(generator.generateValue(nuixCase, query)); + currentCellIndex++; + + // Periodic progress + if(System.currentTimeMillis() - lastProgress >= 1000 ){ + fireProgress(currentCellIndex, totalCells); + lastProgress = System.currentTimeMillis(); + } } } @@ -176,10 +235,20 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon // Apply styles sheet.setStyle(sheet.getCurrentRow()-1, 0, rowCategoryStyle); } + + fireMessage("Autofitting columns..."); sheet.autoFitColumns(); + + fireMessage("Saving..."); xlsx.save(); + fireMessage("Saved"); } - - + public ColorRing getColCategoryColorRing() { + return colCategoryColorRing; + } + + public void setColCategoryColorRing(ColorRing colCategoryColorRing) { + this.colCategoryColorRing = colCategoryColorRing; + } } diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportProgressCallback.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportProgressCallback.java new file mode 100644 index 0000000..762ad35 --- /dev/null +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportProgressCallback.java @@ -0,0 +1,5 @@ +package com.nuix.superutilities.reporting; + +public interface IntersectionReportProgressCallback { + public void progressUpdated(int current, int total); +} diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java index cae3dd7..515c45c 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/ScriptedColumnValueGenerator.java @@ -21,6 +21,12 @@ public ScriptedColumnValueGenerator(String label, BiFunction this.label = label; this.expression = expression; } + + public ScriptedColumnValueGenerator(String label, String categoryLabel, BiFunction expression) { + this.label = label; + this.columnLabel = categoryLabel; + this.expression = expression; + } @Override public Object generateValue(Case nuixCase, String query) { diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java index d10c7f6..7ecdfc5 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/SimpleWorksheet.java @@ -102,6 +102,10 @@ public void autoFitColumns() throws Exception { worksheet.autoFitColumns(); } + public void autoFitRow(int row) throws Exception { + worksheet.autoFitRow(row); + } + public void appendRow(List rowValues) { for (int c = 0; c < rowValues.size(); c++) { Object rowValue = rowValues.get(c); From 7825457866ae81f7b225a7d5e7261e6f4610e5fa Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Thu, 10 Jan 2019 09:56:40 -0800 Subject: [PATCH 5/6] Sheet generation reports how long it took --- .../reporting/IntersectionReport.java | 16 ++++++++++++---- .../IntersectionReportSheetConfiguration.java | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java index dc0694e..7b01826 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReport.java @@ -11,6 +11,7 @@ import com.aspose.cells.BackgroundType; import com.aspose.cells.Color; import com.aspose.cells.Style; +import com.nuix.superutilities.misc.FormatUtility; import nuix.Case; @@ -142,6 +143,7 @@ public IntersectionReport(String file) throws Exception { * @throws Exception Thrown if there are errors */ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetConfiguration sheetConfig) throws Exception { + long generationStarted = System.currentTimeMillis(); colCategoryColorRing.restart(); SimpleWorksheet sheet = xlsx.getSheet(sheetName); @@ -226,10 +228,6 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon } } - //TESTING -// List stringRowValues = rowValues.stream().map(v -> v.toString()).collect(Collectors.toList()); -// logger.info(String.join("\t", stringRowValues)); - sheet.appendRow(rowValues, rowGeneralStyle); // Apply styles @@ -239,9 +237,19 @@ public void generate(Case nuixCase, String sheetName, IntersectionReportSheetCon fireMessage("Autofitting columns..."); sheet.autoFitColumns(); + if(sheetConfig.getFreezePanes() == true) { + // Freeze first column and top 2 rows + sheet.getAsposeWorksheet().freezePanes(2, 1, 2, 1); + } + fireMessage("Saving..."); xlsx.save(); fireMessage("Saved"); + + long generationFinished = System.currentTimeMillis(); + long elapsedMillis = generationFinished - generationStarted; + long elapsedSeconds = elapsedMillis / 1000; + fireMessage("Sheet generated in "+FormatUtility.getInstance().secondsToElapsedString(elapsedSeconds)); } public ColorRing getColCategoryColorRing() { diff --git a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java index 1f3c30e..5e0a346 100644 --- a/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java +++ b/Java/src/main/java/com/nuix/superutilities/reporting/IntersectionReportSheetConfiguration.java @@ -14,6 +14,7 @@ public class IntersectionReportSheetConfiguration { private String rowCategoryLabel = "Term"; private String colPrimaryCategoryLabel = "Column Category"; private String scopeQuery = ""; + private boolean freezePanes = false; /*** * Gets the list of criteria used for each row. @@ -163,4 +164,20 @@ public String getScopeQuery() { public void setScopeQuery(String scopeQuery) { this.scopeQuery = scopeQuery; } + + /*** + * Gets whether "freeze panes" will be applied to this sheet. When true, first column and first 2 rows will be frozen into place. + * @return Whether "freeze panes" will be applied to this sheet. + */ + public boolean getFreezePanes() { + return freezePanes; + } + + /*** + * Sets whether "freeze panes" will be applied to this sheet. When true, first column and first 2 rows will be frozen into place. + * @param freezePanes Whether "freeze panes" will be applied to this sheet. + */ + public void setFreezePanes(boolean freezePanes) { + this.freezePanes = freezePanes; + } } From 93e19ca960150d67f0936f0293cd5d8064638291 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Thu, 24 Jan 2019 13:57:50 -0800 Subject: [PATCH 6/6] Update BulkCaseProcessor.java --- .../com/nuix/superutilities/cases/BulkCaseProcessor.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Java/src/main/java/com/nuix/superutilities/cases/BulkCaseProcessor.java b/Java/src/main/java/com/nuix/superutilities/cases/BulkCaseProcessor.java index d54a079..46b3e9c 100644 --- a/Java/src/main/java/com/nuix/superutilities/cases/BulkCaseProcessor.java +++ b/Java/src/main/java/com/nuix/superutilities/cases/BulkCaseProcessor.java @@ -169,8 +169,7 @@ public void withEachCase(Case currentCaseFromGui, CaseConsumer caseWorkFunction) } } catch (Exception e) { // User code threw exception but didnt catch it - logger.error("Error in user provided case work function:"); - logger.error(e); + logger.error("Error in user provided case work function:",e); if(userFunctionErrorCallback != null){ WorkFunctionErrorEvent userFunctionErrorInfo = new WorkFunctionErrorEvent(caseInfo, e); userFunctionErrorCallback.accept(userFunctionErrorInfo); @@ -192,8 +191,7 @@ public void withEachCase(Case currentCaseFromGui, CaseConsumer caseWorkFunction) } } catch (Exception e) { - logger.error("Error opening case: "); - logger.error(e); + logger.error("Error opening case: ",e); // On case open error notify callback if was provided if(caseErrorCallback != null){ CaseOpenErrorEvent caseErrorInfo = new CaseOpenErrorEvent(caseInfo, e);