From 9b8743b5c7b7f1ae9f93b85bdbbc62a32cebfdb4 Mon Sep 17 00:00:00 2001
From: Eddie Groh <groh@fortiss.org>
Date: Mon, 18 Sep 2023 19:16:31 +0200
Subject: [PATCH] Added better csv export, better data handling and update
 listener

Issue-Ref: 4310
Issue-Url: https://git.fortiss.org/af3/af3/-/issues/4310

Signed-off-by: Eddie Groh <groh@fortiss.org>
---
 .../ext/quality/ui/view/fx/MetricsViewFx.fxml |  4 +-
 .../quality/ui/ModelQualityUIActivator.java   |  4 +
 .../ui/view/fx/ModelQualityFXController.java  | 55 +++++++++++-
 .../ext/quality/MetricUpdateListener.java     | 33 +++++++
 .../ext/quality/data/DataRootElement.java     | 87 +++++++++++++++++++
 .../ext/quality/data/MetricDataManager.java   | 10 +--
 .../quality/service/IModelQualityService.java | 10 +++
 .../quality/service/ModelQualityService.java  | 34 ++++++--
 .../ext/quality/storage/CSVFileWriter.java    | 26 +++---
 9 files changed, 235 insertions(+), 28 deletions(-)
 create mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/MetricUpdateListener.java
 create mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java

diff --git a/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml b/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml
index 68f373c7c..f9f1b276a 100644
--- a/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml
+++ b/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml
@@ -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>
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java
index 47787eb29..2f47db82b 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java
@@ -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.");
 	}
 
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
index 4272d7043..501a2b369 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
@@ -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");
+		});
+	}
 }
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/MetricUpdateListener.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/MetricUpdateListener.java
new file mode 100644
index 000000000..f72bf91fe
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/MetricUpdateListener.java
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------+
+| 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);
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java
new file mode 100644
index 000000000..d54ab629f
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java
@@ -0,0 +1,87 @@
+/*-------------------------------------------------------------------------+
+| 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;
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricDataManager.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricDataManager.java
index 36dc2d044..f9e2d04ed 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricDataManager.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricDataManager.java
@@ -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;
 	}
 }
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
index 5168db337..f653e228c 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
@@ -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);
 }
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
index 75d1301f6..d02483b84 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
@@ -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} */
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
index 3798d6cfd..45559ca85 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
@@ -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 -> {
-- 
GitLab