Skip to content
Snippets Groups Projects
Commit 9b8743b5 authored by Eddie Groh's avatar Eddie Groh
Browse files

Added better csv export, better data handling and update listener

Issue-Ref: 4310
Issue-Url: af3#4310



Signed-off-by: default avatarEddie Groh <groh@fortiss.org>
parent fdf98d93
No related branches found
No related tags found
1 merge request!210Setting up Metric extraction plugin for AF3 : Issue 4310
Showing
with 235 additions and 28 deletions
......@@ -11,8 +11,9 @@
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Text?>
<SplitPane fx:id="metricsSplitPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" orientation="VERTICAL" prefHeight="400.0" prefWidth="1035.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1">
<SplitPane fx:id="metricsSplitPane" dividerPositions="0.5" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" orientation="VERTICAL" prefHeight="400.0" prefWidth="1035.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1">
<items>
<TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
<tabs>
......@@ -67,5 +68,6 @@
</Tab>
</tabs>
</TabPane>
<Text fx:id="bottomText" strokeType="OUTSIDE" strokeWidth="0.0" text="Save model to refresh" />
</items>
</SplitPane>
......@@ -19,6 +19,8 @@ import static org.eclipse.jface.resource.ResourceLocator.imageDescriptorFromBund
import org.eclipse.core.runtime.Plugin;
import org.eclipse.jface.resource.ImageDescriptor;
import org.fortiss.tooling.ext.quality.service.IModelQualityService;
import org.fortiss.tooling.ext.quality.ui.view.fx.ModelQualityFXViewPart;
import org.osgi.framework.BundleContext;
/**
......@@ -39,6 +41,8 @@ public class ModelQualityUIActivator extends Plugin {
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
IModelQualityService.getInstance()
.registerMetricUpdateListener(ModelQualityFXViewPart.getMetricsFXController());
System.out.println("[Plugin] " + PLUGIN_ID + " started.");
}
......
......@@ -28,6 +28,7 @@ import static org.fortiss.tooling.kernel.ui.util.SelectionUtils.checkAndPickFirs
import static org.fortiss.tooling.kernel.utils.EcoreUtils.getFirstParentWithType;
import java.text.DecimalFormat;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -40,6 +41,7 @@ import org.fortiss.tooling.common.ui.javafx.layout.CompositeFXControllerBase;
import org.fortiss.tooling.common.ui.javafx.style.FillStyle;
import org.fortiss.tooling.common.ui.javafx.style.FontStyle;
import org.fortiss.tooling.common.ui.javafx.style.LineStyle;
import org.fortiss.tooling.ext.quality.MetricUpdateListener;
import org.fortiss.tooling.ext.quality.data.MetricDataManager;
import org.fortiss.tooling.ext.quality.data.MetricKey;
import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
......@@ -54,6 +56,7 @@ import org.fortiss.tooling.spiderchart.style.ChartStyle;
import org.fortiss.tooling.spiderchart.style.DataSeriesStyle;
import org.fortiss.tooling.spiderchart.style.LegendStyle;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
......@@ -64,6 +67,7 @@ import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Text;
/**
* FX Controller for the Metrics view.
......@@ -73,7 +77,7 @@ import javafx.scene.layout.BorderPane;
@SuppressWarnings("unchecked")
public class ModelQualityFXController extends CompositeFXControllerBase<SplitPane, Node>
implements ISelectionListener {
implements ISelectionListener, MetricUpdateListener {
/** ChoiceBox for selecting the metric which shall be displayed. */
@FXML
......@@ -91,6 +95,13 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
@FXML
private ScatterChart<Number, Number> scatterChart;
/** {@link Text} at the bottom of the view. */
@FXML
private Text bottomText;
/** Time format when displaying updating time. */
private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
/** {@inheritDoc} */
@Override
public String getFXMLLocation() {
......@@ -135,13 +146,17 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
return;
}
var root_provider = manager.getRootNodesMap().get(currentRootElement);
var rootDataElement = manager.getDataRootElementMap().get(currentRootElement);
if(root_provider == null) {
updateCharts("Currently no metrics available, try generating them");
if(rootDataElement == null) {
bottomText.setText(
"Save model or select \"Export Metrics\" in project options to generate metrics");
updateCharts("No metrics available");
return;
}
bottomText.setText("Metrics last updated: " +
timeFormat.format(rootDataElement.getLastRefresh()) + ". Save model to refresh");
lastSelectedElement = selected;
updateCharts("Selection does not have metric information");
}
......@@ -184,6 +199,12 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
}
}
if(choiceBox.getValue() == null && !choices.isEmpty()) {
// Set choice box to first element in choices, if there are any choices and none
// is currently selected
choiceBox.setValue(choices.get(0));
}
if(!node.getChildren().isEmpty()) {
if(choiceBox.getValue() != null) {
......@@ -233,6 +254,9 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
} else {
message = "Selection does not have metric information";
}
} else {
// Nothing selected, so remove the bottom text
bottomText.setText("");
}
borderPane.setCenter(new Label(message));
pieChart.getData().clear();
......@@ -300,4 +324,27 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
SpiderChartViewer viewer = new SpiderChartViewer(spiderChart, chartStyle);
return viewer;
}
/** {@inheritDoc} */
@Override
public void metricUpdate(IProjectRootElement updatedElement) {
Platform.runLater(() -> {
if(lastSelectedElement == null) {
// If nothing is selected, there is nothing to update
return;
}
var rootDataElement = ModelQualityService.getInstance().getMetricDataManagerInstance()
.getDataRootElementMap().get(updatedElement);
if(rootDataElement != null) {
bottomText.setText("Metrics last updated: " +
timeFormat.format(rootDataElement.getLastRefresh()) +
". Save model to refresh");
} else {
bottomText.setText(
"Save model or select \"Export Metrics\" in project options to generate metrics");
}
updateCharts("Selection does not have metric information");
});
}
}
/*-------------------------------------------------------------------------+
| Copyright 2023 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.ext.quality;
import org.fortiss.tooling.kernel.model.IProjectRootElement;
/**
* Listener getting called when metrics update
*
* @author groh
*/
public interface MetricUpdateListener {
/**
* Callback for metric update. This method will get called every time the metrics are updated.
*
* @param updatedElement
* the corresponding root element which was updated
*/
public void metricUpdate(IProjectRootElement updatedElement);
}
/*-------------------------------------------------------------------------+
| Copyright 2023 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.ext.quality.data;
import java.time.LocalDateTime;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
/**
* Storage element corresponding to a {@Link IProjectRootElement}. It stores the last refresh time
* as {@link LocalDateTime}, its respective {@link ITopLevelElement} and the {@link MetricTreeNode}
* storing the data
*
* @author groh
*/
public class DataRootElement {
/** the last time a metric tree was refreshed */
private LocalDateTime lastRefresh;
/** the last time the metric tree was refreshed */
private ITopLevelElement topLevelElement;
/** Root {@link MetricTreeNode} for the metric tree containg all data */
private MetricTreeNode rootNode;
/**
* Constructor: Creates a new instance of the {@link DataRootElement}.
*
* @param lastRefresh
* the last time the rootNode was refreshed
* @param topLevelElement
* the {@link ITopLevelElement} of the project
* @param rootNode
* the root node of the metric data tree
*/
public DataRootElement(LocalDateTime lastRefresh, ITopLevelElement topLevelElement,
MetricTreeNode rootNode) {
super();
this.lastRefresh = lastRefresh;
this.topLevelElement = topLevelElement;
this.rootNode = rootNode;
}
/** Returns lastRefresh. */
public LocalDateTime getLastRefresh() {
return lastRefresh;
}
/** Sets lastRefresh. */
public void setLastRefresh(LocalDateTime lastRefresh) {
this.lastRefresh = lastRefresh;
}
/** Returns topLevelElement. */
public ITopLevelElement getTopLevelElement() {
return topLevelElement;
}
/** Sets topLevelElement. */
public void setTopLevelElement(ITopLevelElement topLevelElement) {
this.topLevelElement = topLevelElement;
}
/** Returns rootNode. */
public MetricTreeNode getRootNode() {
return rootNode;
}
/** Sets rootNode. */
public void setRootNode(MetricTreeNode metricTreeNode) {
this.rootNode = metricTreeNode;
}
}
......@@ -32,13 +32,13 @@ public class MetricDataManager {
private Map<EObject, MetricTreeNode> treeNodeLookupTable;
/** a map to lookup the corresponding root tree node for a project */
private Map<IProjectRootElement, MetricTreeNode> root_nodes;
private Map<IProjectRootElement, DataRootElement> dataRootElementMap;
/**
* Constructor: Creates a new instance of the MetricDataManager.
*/
public MetricDataManager() {
root_nodes = new HashMap<>();
dataRootElementMap = new HashMap<>();
treeNodeLookupTable = new HashMap<>();
}
......@@ -51,9 +51,9 @@ public class MetricDataManager {
/**
* @return the map between {@link IProjectRootElement} and the corresponding root
* {@link MetricTreeNode}.
* {@link DataRootElement}.
*/
public Map<IProjectRootElement, MetricTreeNode> getRootNodesMap() {
return root_nodes;
public Map<IProjectRootElement, DataRootElement> getDataRootElementMap() {
return dataRootElementMap;
}
}
......@@ -17,6 +17,7 @@ package org.fortiss.tooling.ext.quality.service;
import org.eclipse.emf.ecore.EObject;
import org.fortiss.tooling.ext.quality.IMetricProvider;
import org.fortiss.tooling.ext.quality.MetricUpdateListener;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
/**
......@@ -36,4 +37,13 @@ public interface IModelQualityService {
/** Schedules an analysis of the metrics and processes them. */
void scheduleMetricCollection(ITopLevelElement top);
/**
* Registers the provided {@link MetricUpdateListener} to be called when the metrics are
* updated.
*
* @param listener
* to be registered
*/
void registerMetricUpdateListener(MetricUpdateListener listener);
}
......@@ -17,9 +17,12 @@ package org.fortiss.tooling.ext.quality.service;
import static java.util.Collections.emptyList;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Queue;
......@@ -35,6 +38,8 @@ import org.eclipse.emf.ecore.EObject;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.ext.quality.GraphMetricsProvider;
import org.fortiss.tooling.ext.quality.IMetricProvider;
import org.fortiss.tooling.ext.quality.MetricUpdateListener;
import org.fortiss.tooling.ext.quality.data.DataRootElement;
import org.fortiss.tooling.ext.quality.data.MetricDataManager;
import org.fortiss.tooling.ext.quality.data.MetricKey;
import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
......@@ -84,6 +89,9 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
/** Instance of the MetricDataManager. */
private MetricDataManager metricDataManagerInstance = new MetricDataManager();
/** List of {@link MetricUpdateListener} which are called when the metrics are updated */
private List<MetricUpdateListener> metricUpdateListeners = new ArrayList<>();
/** Top-level elements queued to be processed by the metric collector. */
private final Queue<ITopLevelElement> queuedProcessableElements =
new ConcurrentLinkedQueue<ITopLevelElement>();
......@@ -116,6 +124,12 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
metricCollectorJob.schedule();
}
/** {@inheritDoc} */
@Override
public void registerMetricUpdateListener(MetricUpdateListener listener) {
this.metricUpdateListeners.add(listener);
}
/** {@inheritDoc} */
@Override
public void startService() {
......@@ -146,14 +160,14 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
* @param topLvl
* the {@link ITopLevelElement} on which the analysis should be performed
*/
private void performMetricCollection(ITopLevelElement topLvl) {
private void performMetricCollection(ITopLevelElement topLevelElement) {
// Sort root elements, as some metrics are saved into trees from other root elements
// To avoid these problems, the elements have to be processed in a certain order
// Currently the only element affected by this is the allocation table, which is
// why push it to the end of the list
List<IProjectRootElement> rootElements = new ArrayList<>();
rootElements.addAll(EcoreUtils.getChildrenWithType(topLvl.getRootModelElement(),
rootElements.addAll(EcoreUtils.getChildrenWithType(topLevelElement.getRootModelElement(),
IProjectRootElement.class));
String allocationTableClassName =
......@@ -169,10 +183,9 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
}
return 0;
});
Map<IProjectRootElement, DataRootElement> updatedElements = new HashMap<>();
for(IProjectRootElement rootElement : rootElements) {
var root_nodes = metricDataManagerInstance.getRootNodesMap();
MetricTreeNode rootNode = new MetricTreeNode();
if(rootElement instanceof IHierarchicElement) {
......@@ -207,12 +220,16 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
.filter(v -> v.getSeverity() == ESeverity.WARNING).count());
}
}
root_nodes.put(rootElement, rootNode);
DataRootElement dataRootElement =
new DataRootElement(LocalDateTime.now(), topLevelElement, rootNode);
metricDataManagerInstance.getDataRootElementMap().put(rootElement, dataRootElement);
updatedElements.put(rootElement, dataRootElement);
metricDataManagerInstance.getTreeNodeLookupTable().put(rootElement, rootNode);
metricUpdateListeners.forEach(l -> l.metricUpdate(rootElement));
}
// Store all currently saved metrics to the csv
CSVFileWriter.metricExtractionToCSV(metricDataManagerInstance.getRootNodesMap());
CSVFileWriter.metricExtractionToCSV(updatedElements);
}
/**
......@@ -363,7 +380,8 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
/** {@inheritDoc} */
@Override
public void topLevelElementLoaded(ITopLevelElement element) {
// Nothing done
// Schedule metric collection when loading projects
scheduleMetricCollection(element);
}
/** {@inheritDoc} */
......
......@@ -32,8 +32,8 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.fortiss.tooling.ext.quality.data.DataRootElement;
import org.fortiss.tooling.ext.quality.data.MetricKey;
import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
import org.fortiss.tooling.kernel.model.IProjectRootElement;
/**
......@@ -56,7 +56,7 @@ public class CSVFileWriter {
* to convert into csv
*
*/
public static void metricExtractionToCSV(Map<IProjectRootElement, MetricTreeNode> map) {
public static void metricExtractionToCSV(Map<IProjectRootElement, DataRootElement> map) {
// check if there is any data to export
if(map == null || map.isEmpty()) {
......@@ -86,8 +86,8 @@ public class CSVFileWriter {
} catch(IOException e) {
e.printStackTrace();
}
writeMetricsToFile(map, allKeys, path, createNewIndex);
}
writeMetricsToFile(map, allKeys, path, createNewIndex);
}
/**
......@@ -103,14 +103,15 @@ public class CSVFileWriter {
* @param createNewIndex
* if a index should be written before writing any values
*/
private static void writeMetricsToFile(Map<IProjectRootElement, MetricTreeNode> map,
private static void writeMetricsToFile(Map<IProjectRootElement, DataRootElement> map,
List<String> allKeys, Path path, boolean createNewIndex) {
try(var writer = new BufferedWriter(new FileWriter(path.toFile(), true))) {
if(createNewIndex) {
// Create new index and write it into the first line of the file
allKeys.add("timestamp");
allKeys.add("root_name");
allKeys.add("project_name");
allKeys.add("root_element_name");
allKeys.add("name");
allKeys.add("children");
// Collect all MetricKeys, convert them to a string and append them to the allKeys
......@@ -123,15 +124,20 @@ public class CSVFileWriter {
writer.write("\n");
}
// Remove first 4 keys as they are not provided by the provider
List<String> valueKeys = allKeys.subList(4, allKeys.size());
List<String> valueKeys = allKeys.subList(5, allKeys.size());
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String formattedDateTime = dateTime.format(formatter);
map.forEach((rootProject, rootNode) -> {
map.forEach((rootElement, dataRootElement) -> {
// iterate over all project roots
String rootName = rootProject.getName();
String rootName = rootElement.getClass().getSimpleName();
var topLevelElement = dataRootElement.getTopLevelElement();
String projectName = topLevelElement.getSaveableName();
var rootNode = dataRootElement.getRootNode();
rootNode.traverseTree(node -> {
var integers = node.getStoredIntegers();
......@@ -143,8 +149,8 @@ public class CSVFileWriter {
.map(id -> id.toString()).reduce((a, b) -> a + " " + b).orElse("");
// These 4 data entries are just concatenated in front
var startStream =
Stream.of(formattedDateTime, rootName, node.getName(), children_ids);
var startStream = Stream.of(formattedDateTime, projectName, rootName,
node.getName(), children_ids);
// Collect all values from provider in the correct order and combine
var values = Stream.concat(startStream, valueKeys.stream()
.map(String::toUpperCase).map(MetricKey::valueOf).map(k -> {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment