Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add csv export (fixes #273) #643

Merged
merged 4 commits into from
Oct 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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