Skip to content

Commit

Permalink
add csv export (fixes #273, via #643)
Browse files Browse the repository at this point in the history
  • Loading branch information
vbragin authored and baev committed Oct 23, 2017
1 parent c8b945c commit 2524d15
Show file tree
Hide file tree
Showing 24 changed files with 726 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.qameta.allure.category;

import com.fasterxml.jackson.core.type.TypeReference;
import io.qameta.allure.Aggregator;
import io.qameta.allure.CommonCsvExportAggregator;
import io.qameta.allure.CommonJsonAggregator;
import io.qameta.allure.CompositeAggregator;
import io.qameta.allure.Reader;
import io.qameta.allure.Widget;
import io.qameta.allure.context.JacksonContext;
import io.qameta.allure.core.Configuration;
import io.qameta.allure.core.LaunchResults;
import io.qameta.allure.core.ResultsVisitor;
import io.qameta.allure.csv.CsvExportCategory;
import io.qameta.allure.entity.Status;
import io.qameta.allure.entity.TestResult;
import io.qameta.allure.tree.DefaultTreeLayer;
Expand All @@ -20,7 +23,6 @@

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -45,55 +47,46 @@
* @since 2.0
*/
@SuppressWarnings("PMD.ExcessiveImports")
public class CategoriesPlugin implements Aggregator, Reader, Widget {
public class CategoriesPlugin extends CompositeAggregator implements Reader, Widget {

public static final String CATEGORIES_BLOCK_NAME = "categories";
public static final String CATEGORIES = "categories";

public static final Category FAILED_TESTS = new Category().setName("Product defects");

public static final Category BROKEN_TESTS = new Category().setName("Test defects");

private static final String CATEGORIES_FILE_NAME = "categories.json";
public static final String JSON_FILE_NAME = "categories.json";

public static final String CSV_FILE_NAME = "categories.csv";

//@formatter:off
private static final TypeReference<List<Category>> CATEGORIES_TYPE =
new TypeReference<List<Category>>() {};
//@formatter:on

public CategoriesPlugin() {
super(Arrays.asList(new JsonAggregator(), new CsvExportAggregator()));
}

@Override
public void readResults(final Configuration configuration,
final ResultsVisitor visitor,
final Path directory) {
final JacksonContext context = configuration.requireContext(JacksonContext.class);
final Path categoriesFile = directory.resolve(CATEGORIES_FILE_NAME);
final Path categoriesFile = directory.resolve(JSON_FILE_NAME);
if (Files.exists(categoriesFile)) {
try (InputStream is = Files.newInputStream(categoriesFile)) {
final List<Category> categories = context.getValue().readValue(is, CATEGORIES_TYPE);
visitor.visitExtra(CATEGORIES_BLOCK_NAME, categories);
visitor.visitExtra(CATEGORIES, categories);
} catch (IOException e) {
visitor.error("Could not read categories file " + categoriesFile, e);
}
}
}

@Override
public void aggregate(final Configuration configuration,
final List<LaunchResults> launchesResults,
final Path outputDirectory) throws IOException {

addCategoriesForResults(launchesResults);

final JacksonContext jacksonContext = configuration.requireContext(JacksonContext.class);
final Path dataFolder = Files.createDirectories(outputDirectory.resolve("data"));
final Path dataFile = dataFolder.resolve("categories.json");
try (OutputStream os = Files.newOutputStream(dataFile)) {
jacksonContext.getValue().writeValue(os, getData(launchesResults));
}
}

@Override
public String getName() {
return "categories";
return CATEGORIES;
}

@Override
Expand All @@ -102,19 +95,18 @@ public Object getData(final Configuration configuration, final List<LaunchResult
final List<TreeWidgetItem> items = data.getChildren().stream()
.filter(TestResultTreeGroup.class::isInstance)
.map(TestResultTreeGroup.class::cast)
.map(this::toWidgetItem)
.map(CategoriesPlugin::toWidgetItem)
.sorted(Comparator.comparing(TreeWidgetItem::getStatistic, comparator()).reversed())
.limit(10)
.collect(Collectors.toList());
return new TreeWidgetData().setItems(items).setTotal(data.getChildren().size());
}

@SuppressWarnings("PMD.DefaultPackage")
/* default */ Tree<TestResult> getData(final List<LaunchResults> launchResults) {

/* default */ static Tree<TestResult> getData(final List<LaunchResults> launchResults) {

// @formatter:off
final Tree<TestResult> categories = new TestResultTree("categories", this::groupByCategories);
final Tree<TestResult> categories = new TestResultTree(CATEGORIES, CategoriesPlugin::groupByCategories);
// @formatter:on

launchResults.stream()
Expand All @@ -126,29 +118,29 @@ public Object getData(final Configuration configuration, final List<LaunchResult
}

@SuppressWarnings("PMD.DefaultPackage")
/* default */ void addCategoriesForResults(final List<LaunchResults> launchesResults) {
/* default */ static void addCategoriesForResults(final List<LaunchResults> launchesResults) {
launchesResults.forEach(launch -> {
final List<Category> categories = launch.getExtra(CATEGORIES_BLOCK_NAME, Collections::emptyList);
final List<Category> categories = launch.getExtra(CATEGORIES, Collections::emptyList);
launch.getResults().forEach(result -> {
final List<Category> resultCategories = result.getExtraBlock(CATEGORIES_BLOCK_NAME, new ArrayList<>());
final List<Category> resultCategories = result.getExtraBlock(CATEGORIES, new ArrayList<>());
categories.forEach(category -> {
if (matches(result, category)) {
resultCategories.add(category);
}
});
if (resultCategories.isEmpty() && Status.FAILED.equals(result.getStatus())) {
result.getExtraBlock(CATEGORIES_BLOCK_NAME, new ArrayList<Category>()).add(FAILED_TESTS);
result.getExtraBlock(CATEGORIES, new ArrayList<Category>()).add(FAILED_TESTS);
}
if (resultCategories.isEmpty() && Status.BROKEN.equals(result.getStatus())) {
result.getExtraBlock(CATEGORIES_BLOCK_NAME, new ArrayList<Category>()).add(BROKEN_TESTS);
result.getExtraBlock(CATEGORIES, new ArrayList<Category>()).add(BROKEN_TESTS);
}
});
});
}

protected List<TreeLayer> groupByCategories(final TestResult testResult) {
protected static List<TreeLayer> groupByCategories(final TestResult testResult) {
final Set<String> categories = testResult
.<List<Category>>getExtraBlock(CATEGORIES_BLOCK_NAME, new ArrayList<>())
.<List<Category>>getExtraBlock(CATEGORIES, new ArrayList<>())
.stream()
.map(Category::getName)
.collect(Collectors.toSet());
Expand Down Expand Up @@ -178,10 +170,51 @@ private static boolean matches(final String message, final String pattern) {
return Pattern.compile(pattern, Pattern.DOTALL).matcher(message).matches();
}

protected TreeWidgetItem toWidgetItem(final TestResultTreeGroup group) {
protected static TreeWidgetItem toWidgetItem(final TestResultTreeGroup group) {
return new TreeWidgetItem()
.setUid(group.getUid())
.setName(group.getName())
.setStatistic(calculateStatisticByLeafs(group));
}

@Override
public void aggregate(final Configuration configuration,
final List<LaunchResults> launchesResults,
final Path outputDirectory) throws IOException {
addCategoriesForResults(launchesResults);
super.aggregate(configuration, launchesResults, outputDirectory);
}

private static class JsonAggregator extends CommonJsonAggregator {

JsonAggregator() {
super(JSON_FILE_NAME);
}

@Override
protected Tree<TestResult> getData(final List<LaunchResults> launchResults) {
return CategoriesPlugin.getData(launchResults);
}
}

private static class CsvExportAggregator extends CommonCsvExportAggregator<CsvExportCategory> {

CsvExportAggregator() {
super(CSV_FILE_NAME, CsvExportCategory.class);
}

@Override
protected List<CsvExportCategory> getData(final List<LaunchResults> launchesResults) {
final List<CsvExportCategory> exportLabels = new ArrayList<>();
final Tree<TestResult> data = CategoriesPlugin.getData(launchesResults);
final List<TreeWidgetItem> items = data.getChildren().stream()
.filter(TestResultTreeGroup.class::isInstance)
.map(TestResultTreeGroup.class::cast)
.map(CategoriesPlugin::toWidgetItem)
.sorted(Comparator.comparing(TreeWidgetItem::getStatistic, comparator()).reversed())
.collect(Collectors.toList());
items.forEach(item -> exportLabels.add(new CsvExportCategory(item)));
return exportLabels;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package io.qameta.allure.suites;

import io.qameta.allure.Aggregator;
import io.qameta.allure.CommonCsvExportAggregator;
import io.qameta.allure.CommonJsonAggregator;
import io.qameta.allure.CompositeAggregator;
import io.qameta.allure.Widget;
import io.qameta.allure.context.JacksonContext;
import io.qameta.allure.core.Configuration;
import io.qameta.allure.core.LaunchResults;
import io.qameta.allure.csv.CsvExportSuite;
import io.qameta.allure.entity.TestResult;
import io.qameta.allure.tree.TestResultTree;
import io.qameta.allure.tree.TestResultTreeGroup;
import io.qameta.allure.tree.Tree;
import io.qameta.allure.tree.TreeWidgetData;
import io.qameta.allure.tree.TreeWidgetItem;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
Expand All @@ -34,26 +32,26 @@
*
* @since 2.0
*/
public class SuitesPlugin implements Aggregator, Widget {
public class SuitesPlugin extends CompositeAggregator implements Widget {

@Override
public void aggregate(final Configuration configuration,
final List<LaunchResults> launchesResults,
final Path outputDirectory) throws IOException {
final JacksonContext jacksonContext = configuration.requireContext(JacksonContext.class);
final Path dataFolder = Files.createDirectories(outputDirectory.resolve("data"));
final Path dataFile = dataFolder.resolve("suites.json");
try (OutputStream os = Files.newOutputStream(dataFile)) {
jacksonContext.getValue().writeValue(os, getData(launchesResults));
}
private static final String SUITES = "suites";

/** Name of the json file. */
protected static final String JSON_FILE_NAME = "suites.json";

/** Name of the csv file. */
protected static final String CSV_FILE_NAME = "suites.csv";

public SuitesPlugin() {
super(Arrays.asList(new JsonAggregator(), new CsvExportAggregator()));
}

@SuppressWarnings("PMD.DefaultPackage")
/* default */ Tree<TestResult> getData(final List<LaunchResults> launchResults) {
static /* default */ Tree<TestResult> getData(final List<LaunchResults> launchResults) {

// @formatter:off
final Tree<TestResult> xunit = new TestResultTree(
"suites",
SUITES,
testResult -> groupByLabels(testResult, PARENT_SUITE, SUITE, SUB_SUITE)
);
// @formatter:on
Expand Down Expand Up @@ -81,13 +79,39 @@ public Object getData(final Configuration configuration, final List<LaunchResult

@Override
public String getName() {
return "suites";
return SUITES;
}

protected TreeWidgetItem toWidgetItem(final TestResultTreeGroup group) {
private TreeWidgetItem toWidgetItem(final TestResultTreeGroup group) {
return new TreeWidgetItem()
.setUid(group.getUid())
.setName(group.getName())
.setStatistic(calculateStatisticByLeafs(group));
}

private static class JsonAggregator extends CommonJsonAggregator {

JsonAggregator() {
super(JSON_FILE_NAME);
}

@Override
protected Tree<TestResult> getData(final List<LaunchResults> launchResults) {
return SuitesPlugin.getData(launchResults);
}
}

private static class CsvExportAggregator extends CommonCsvExportAggregator<CsvExportSuite> {

CsvExportAggregator() {
super(CSV_FILE_NAME, CsvExportSuite.class);
}

@Override
protected List<CsvExportSuite> getData(final List<LaunchResults> launchesResults) {
return launchesResults.stream()
.flatMap(launch -> launch.getResults().stream())
.map(CsvExportSuite::new).collect(Collectors.toList());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import ErrorSplashView from '../error-splash/ErrorSplashView';
@className('side-by-side')
class TestResultTreeView extends SideBySideView {

initialize({tree, routeState}) {
initialize({tree, routeState, csvUrl}) {
super.initialize();
this.csvUrl = csvUrl;
this.tree = tree;
this.routeState = routeState;
this.listenTo(this.routeState, 'change:treeNode', (_, treeNode) => this.showLeaf(treeNode));
Expand Down Expand Up @@ -39,7 +40,8 @@ class TestResultTreeView extends SideBySideView {
routeState: this.routeState,
treeSorters: [],
tabName: tabName,
baseUrl: baseUrl
baseUrl: baseUrl,
csvUrl: this.csvUrl
});
this.showChildView('left', left);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<div class="{{b 'pane' 'search'}}"></div>
<span class="pane__controls">
<a class="fa fa-info-circle {{b cls 'control'}} {{b cls 'info'}}" data-tooltip="{{t 'component.tree.groups'}}"></a>
{{#if csvUrl}}
<a class="fa fa-download {{b cls 'control'}} {{b cls 'download'}}" data-tooltip="{{t 'component.tree.download'}}"
href="{{csvUrl}}" download></a>
{{/if}}
</span>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import {getSettingsForTreePlugin} from '../../utils/settingsFactory';
class TreeViewContainer extends View {
template = template;

initialize({routeState, state = new Model(), tabName, baseUrl, settings = getSettingsForTreePlugin(baseUrl)}) {
initialize({routeState, state = new Model(), tabName, baseUrl, csvUrl=null, settings = getSettingsForTreePlugin(baseUrl)}) {
this.state = state;
this.routeState = routeState;
this.baseUrl = baseUrl;
this.csvUrl = csvUrl;
this.tabName = tabName;
this.listenTo(this.routeState, 'change:testResultTab', this.render);

this.settings = settings;
}

Expand Down Expand Up @@ -66,7 +66,8 @@ class TreeViewContainer extends View {
tabName: this.tabName,
shownCases: 0,
totalCases: 0,
filtered: false
filtered: false,
csvUrl: this.csvUrl
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@
border-top: 1px solid #ECEFF1;
padding: 8px 0 0 15px;
}
.pane__controls {
display: flex;
}
&__info, &__download {
color: $text-muted-color;
padding: 7px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class TreeView extends View {
uid: this.collection.uid,
tabName: this.tabName,
items: this.collection.toJSON(),
testResultTab: this.routeState.get('testResultTab') || '',
testResultTab: this.routeState.get('testResultTab') || ''
};
}
}
Expand Down
Loading

0 comments on commit 2524d15

Please sign in to comment.