This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 0f7211bede Some improvements to the metrics panel, fixes #6599 (#6600)
0f7211bede is described below
commit 0f7211bede20066fb027608bad11a9e8db5c78fc
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Feb 17 20:14:39 2026 +0100
Some improvements to the metrics panel, fixes #6599 (#6600)
---
.../main/java/org/apache/hop/ui/core/PropsUi.java | 66 +++++
.../org/apache/hop/ui/core/widget/ColumnInfo.java | 210 ++------------
.../file/pipeline/PipelineMetricDisplayUtil.java | 105 +++++++
.../delegates/HopGuiPipelineGridDelegate.java | 306 ++++++++++++++++++++-
.../configuration/tabs/ConfigGuiOptionsTab.java | 146 ++++++++++
.../execution/PipelineExecutionViewer.java | 10 +-
.../core/dialog/messages/messages_en_US.properties | 9 +
.../ui/hopgui/messages/messages_en_US.properties | 33 ++-
8 files changed, 678 insertions(+), 207 deletions(-)
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index bd6705fcea..68eba5018f 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -88,6 +88,15 @@ public class PropsUi extends Props {
private static final String USE_ADVANCED_TERMINAL = "UseAdvancedTerminal";
private static final String RESET_DIALOG_POSITIONS_ON_RESTART =
"ResetDialogPositionsOnRestart";
+ // Metrics panel (pipeline execution grid) – "Show" options
+ private static final String METRICS_PANEL_SHOW_UNITS =
"MetricsPanel.ShowUnits";
+ private static final String METRICS_PANEL_SHOW_INPUT =
"MetricsPanel.ShowInput";
+ private static final String METRICS_PANEL_SHOW_READ =
"MetricsPanel.ShowRead";
+ private static final String METRICS_PANEL_SHOW_OUTPUT =
"MetricsPanel.ShowOutput";
+ private static final String METRICS_PANEL_SHOW_UPDATED =
"MetricsPanel.ShowUpdated";
+ private static final String METRICS_PANEL_SHOW_REJECTED =
"MetricsPanel.ShowRejected";
+ private static final String METRICS_PANEL_SHOW_BUFFERS_INPUT =
"MetricsPanel.ShowBuffersInput";
+
public static final int DEFAULT_MAX_EXECUTION_LOGGING_TEXT_SIZE = 2000000;
private Map<RGB, RGB> contrastingColors;
private static PropsUi instance;
@@ -586,6 +595,63 @@ public class PropsUi extends Props {
setProperty(STRING_SHOW_TABLE_VIEW_TOOLBAR, show ? YES : NO);
}
+ /** Show units in grid cells (default false). */
+ public boolean isMetricsPanelShowUnits() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_UNITS, NO));
+ }
+
+ public void setMetricsPanelShowUnits(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_UNITS, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowInput() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_INPUT, YES));
+ }
+
+ public void setMetricsPanelShowInput(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_INPUT, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowRead() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_READ, YES));
+ }
+
+ public void setMetricsPanelShowRead(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_READ, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowOutput() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_OUTPUT, YES));
+ }
+
+ public void setMetricsPanelShowOutput(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_OUTPUT, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowUpdated() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_UPDATED, YES));
+ }
+
+ public void setMetricsPanelShowUpdated(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_UPDATED, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowRejected() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_REJECTED, YES));
+ }
+
+ public void setMetricsPanelShowRejected(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_REJECTED, show ? YES : NO);
+ }
+
+ public boolean isMetricsPanelShowBuffersInput() {
+ return YES.equalsIgnoreCase(getProperty(METRICS_PANEL_SHOW_BUFFERS_INPUT,
YES));
+ }
+
+ public void setMetricsPanelShowBuffersInput(boolean show) {
+ setProperty(METRICS_PANEL_SHOW_BUFFERS_INPUT, show ? YES : NO);
+ }
+
public static void setLook(Widget widget) {
int style = WIDGET_STYLE_DEFAULT;
if (widget instanceof Table) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
index 6d15f84489..9c75e86935 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/ColumnInfo.java
@@ -18,6 +18,8 @@
package org.apache.hop.ui.core.widget;
import java.util.function.Supplier;
+import lombok.Getter;
+import lombok.Setter;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.value.ValueMetaInteger;
import org.apache.hop.core.row.value.ValueMetaString;
@@ -35,35 +37,39 @@ public class ColumnInfo {
public static final int COLUMN_TYPE_FORMAT = 5;
public static final int COLUMN_TYPE_TEXT_BUTTON = 6;
- private int type;
- private String name;
+ @Getter private final int type;
- private String[] comboValues;
- private Supplier<String[]> comboValueSupplier = () -> comboValues;
- private boolean numeric;
- private String tooltip;
- private Image image;
- private int alignment;
+ @Getter private final String name;
+
+ @Setter private String[] comboValues;
+ @Setter @Getter private Supplier<String[]> comboValueSupplier = () ->
comboValues;
+ @Getter @Setter private boolean numeric;
+ @Getter @Setter private String tooltip;
+ @Getter @Setter private Image image;
+ @Setter @Getter private int alignment;
private boolean readonly;
- private String buttonText;
+ @Setter @Getter private String buttonText;
private boolean hidingNegativeValues;
- private int width = -1;
- private boolean autoResize = true;
+ @Getter @Setter private int width = -1;
+
+ @Getter @Setter private boolean autoResize = true;
- private IValueMeta valueMeta;
+ @Getter @Setter private IValueMeta valueMeta;
private SelectionListener selButton;
- private SelectionListener textVarButtonSelectionListener;
- private ITextVarButtonRenderCallback renderTextVarButtonCallback;
+ @Getter @Setter private SelectionListener textVarButtonSelectionListener;
+
+ @Getter @Setter private ITextVarButtonRenderCallback
renderTextVarButtonCallback;
- private IFieldDisabledListener disabledListener;
+ @Getter @Setter private IFieldDisabledListener disabledListener;
- private boolean usingVariables;
- private boolean passwordField;
+ @Getter @Setter private boolean usingVariables;
- private IComboValuesSelectionListener comboValuesSelectionListener;
- private int fieldTypeColumn;
+ @Getter @Setter private boolean passwordField;
+
+ @Getter @Setter private IComboValuesSelectionListener
comboValuesSelectionListener;
+ @Getter @Setter private int fieldTypeColumn;
/**
* Creates a column info class for use with the TableView class.
@@ -197,77 +203,18 @@ public class ColumnInfo {
readonly = ro;
}
- public void setAlignment(int allign) {
- alignment = allign;
- }
-
- public void setComboValues(String[] cv) {
- comboValues = cv;
- }
-
- public void setComboValueSupplier(Supplier<String[]> comboValueSupplier) {
- this.comboValueSupplier = comboValueSupplier;
- }
-
- public void setButtonText(String bt) {
- buttonText = bt;
- }
-
- public String getName() {
- return name;
- }
-
- public int getType() {
- return type;
- }
-
public String[] getComboValues() {
return comboValueSupplier.get();
}
- /**
- * @return the numeric
- */
- public boolean isNumeric() {
- return numeric;
- }
-
- /**
- * @param numeric the numeric to set
- */
- public void setNumeric(boolean numeric) {
- this.numeric = numeric;
- }
-
public String getToolTip() {
return tooltip;
}
- public Image getImage() {
- return image;
- }
-
- /**
- * Sets the column's image to be displayed.
- *
- * @param image the image to display on the receiver (may be null)
- */
- public void setImage(Image image) {
- this.image = image;
- }
-
- public int getAlignment() {
- return alignment;
- }
-
public boolean isReadOnly() {
return readonly;
}
- public String getButtonText() {
- return buttonText;
- }
-
public void setSelectionAdapter(SelectionListener sb) {
selButton = sb;
}
@@ -288,115 +235,8 @@ public class ColumnInfo {
return hidingNegativeValues;
}
- /**
- * @return the valueMeta
- */
- public IValueMeta getValueMeta() {
- return valueMeta;
- }
-
- /**
- * @param valueMeta the valueMeta to set
- */
- public void setValueMeta(IValueMeta valueMeta) {
- this.valueMeta = valueMeta;
- }
-
- /**
- * @return the usingVariables
- */
- public boolean isUsingVariables() {
- return usingVariables;
- }
-
- /**
- * @param usingVariables the usingVariables to set
- */
- public void setUsingVariables(boolean usingVariables) {
- this.usingVariables = usingVariables;
- }
-
- /**
- * @return the password
- */
- public boolean isPasswordField() {
- return passwordField;
- }
-
- /**
- * @param password the password to set
- */
- public void setPasswordField(boolean password) {
- this.passwordField = password;
- }
-
- public int getFieldTypeColumn() {
- return fieldTypeColumn;
- }
-
- public void setFieldTypeColumn(int fieldTypeColumn) {
- this.fieldTypeColumn = fieldTypeColumn;
- }
-
- /**
- * @return the comboValuesSelectionListener
- */
- public IComboValuesSelectionListener getComboValuesSelectionListener() {
- return comboValuesSelectionListener;
- }
-
- /**
- * @param comboValuesSelectionListener the comboValuesSelectionListener to
set
- */
- public void setComboValuesSelectionListener(
- IComboValuesSelectionListener comboValuesSelectionListener) {
- this.comboValuesSelectionListener = comboValuesSelectionListener;
- }
-
- /**
- * @return the disabledListener
- */
- public IFieldDisabledListener getDisabledListener() {
- return disabledListener;
- }
-
- /**
- * @param disabledListener the disabledListener to set
- */
- public void setDisabledListener(IFieldDisabledListener disabledListener) {
- this.disabledListener = disabledListener;
- }
-
- public SelectionListener getTextVarButtonSelectionListener() {
- return textVarButtonSelectionListener;
- }
-
- public void setTextVarButtonSelectionListener(SelectionListener
textVarButtonSelectionListener) {
- this.textVarButtonSelectionListener = textVarButtonSelectionListener;
- }
-
- public void setRenderTextVarButtonCallback(ITextVarButtonRenderCallback
callback) {
- this.renderTextVarButtonCallback = callback;
- }
-
public boolean shouldRenderTextVarButton() {
return this.renderTextVarButtonCallback == null
|| this.renderTextVarButtonCallback.shouldRenderButton();
}
-
- public int getWidth() {
- return this.width;
- }
-
- /**
- * @return if should be resized to accommodate contents
- */
- public boolean isAutoResize() {
- return autoResize;
- }
-
- /** If should be resized to accommodate contents. Default is
<code>true</code>. */
- public void setAutoResize(boolean resize) {
- this.autoResize = resize;
- }
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
new file mode 100644
index 0000000000..e2d2c238c1
--- /dev/null
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/PipelineMetricDisplayUtil.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.apache.hop.ui.hopgui.file.pipeline;
+
+import org.apache.hop.pipeline.Pipeline;
+import org.apache.hop.pipeline.engine.IEngineMetric;
+
+/**
+ * Utility for displaying pipeline metrics in the UI with appropriate units.
Does not change metric
+ * storage or keys; only used for column headers and labels.
+ */
+public final class PipelineMetricDisplayUtil {
+
+ private PipelineMetricDisplayUtil() {}
+
+ /**
+ * Returns the metric header for display in the metrics tab/grid. The
underlying metric key (e.g.
+ * for lookup) remains the raw header from {@link IEngineMetric#getHeader()}.
+ *
+ * @param metric the engine metric
+ * @param withUnit when true, append unit in parentheses (e.g. "Input
(rows)"); when false, header
+ * only (e.g. "Input")
+ * @return display label
+ */
+ public static String getDisplayHeader(IEngineMetric metric, boolean
withUnit) {
+ if (metric == null) {
+ return "";
+ }
+ if (!withUnit) {
+ return metric.getHeader();
+ }
+ String unit = getUnitForMetric(metric);
+ if (unit == null || unit.isEmpty()) {
+ return metric.getHeader();
+ }
+ return metric.getHeader() + " (" + unit + ")";
+ }
+
+ /**
+ * Returns the metric header with the correct unit for display (same as
getDisplayHeader(metric,
+ * true)).
+ */
+ public static String getDisplayHeaderWithUnit(IEngineMetric metric) {
+ return getDisplayHeader(metric, true);
+ }
+
+ /**
+ * Returns the unit label for a metric (e.g. "rows", "runs") for use in
column headers. Returns
+ * null if the metric has no unit.
+ */
+ public static String getUnitForMetric(IEngineMetric metric) {
+ String code = metric.getCode();
+ if (code == null) {
+ return null;
+ }
+ switch (code) {
+ case Pipeline.METRIC_NAME_INPUT,
+ Pipeline.METRIC_NAME_READ,
+ Pipeline.METRIC_NAME_WRITTEN,
+ Pipeline.METRIC_NAME_OUTPUT,
+ Pipeline.METRIC_NAME_UPDATED,
+ Pipeline.METRIC_NAME_REJECTED,
+ Pipeline.METRIC_NAME_ERROR,
+ Pipeline.METRIC_NAME_BUFFER_IN,
+ Pipeline.METRIC_NAME_BUFFER_OUT:
+ return "rows";
+ case Pipeline.METRIC_NAME_INIT:
+ return "runs";
+ case Pipeline.METRIC_NAME_FLUSH_BUFFER:
+ return "flushes";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the short unit label for a metric for use in grid cell values
(e.g. "r" for rows so the
+ * header can keep "rows"). Returns null if the metric has no unit.
+ */
+ public static String getUnitForMetricCell(IEngineMetric metric) {
+ String unit = getUnitForMetric(metric);
+ if (unit == null) {
+ return null;
+ }
+ if ("rows".equals(unit)) {
+ return "r";
+ }
+ return unit;
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
index 6cae360d41..988f92d402 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineGridDelegate.java
@@ -22,15 +22,19 @@ import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
import lombok.Getter;
import org.apache.hop.core.Const;
import org.apache.hop.core.Props;
+import org.apache.hop.core.config.HopConfig;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
import org.apache.hop.core.row.IValueMeta;
@@ -57,16 +61,22 @@ import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.file.pipeline.PipelineMetricDisplayUtil;
import org.apache.hop.ui.hopgui.selection.HopGuiSelectionTracker;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
@@ -133,6 +143,24 @@ public class HopGuiPipelineGridDelegate {
private Text searchText;
+ /**
+ * Keys for current table columns (e.g. "#", "TransformName", "input") used
to save/restore column
+ * widths. Set when the table is created so we can save widths before
dispose.
+ */
+ private List<String> metricsColumnKeys = null;
+
+ /**
+ * Column widths for the metrics grid (session-only, not persisted). Key =
column key e.g. "#",
+ * "TransformName", "input".
+ */
+ private final Map<String, Integer> metricsColumnWidths = new HashMap<>();
+
+ /**
+ * True while we are programmatically restoring column widths. Resize
listeners must not persist
+ * those changes or they overwrite the user's saved widths.
+ */
+ private boolean restoringColumnWidths = false;
+
/**
* @param hopGui Hop GUI instance
* @param pipelineGraph the pipeline graph that owns this delegate
@@ -341,6 +369,7 @@ public class HopGuiPipelineGridDelegate {
|| pipelineGraph.getPipeline().isStopped())
&& !pipelineGraph.getPipeline().isReadyToStart()) {
ExecutorUtil.cleanup(refreshMetricsTimer, UPDATE_TIME_VIEW +
10);
+ refreshMetricsTimer = null;
}
}
}
@@ -385,11 +414,110 @@ public class HopGuiPipelineGridDelegate {
toolbarWidget.registerGuiPluginObject(this);
toolbarWidget.createToolbarWidgets(toolBarContainer,
GUI_PLUGIN_TOOLBAR_PARENT_ID);
+ addMetricsViewDropdown(toolbar);
addSearchBarToToolbar(toolbar);
toolbar.pack();
}
+ /**
+ * Add a "View" dropdown to the toolbar with checkable items for metrics
panel options (hide
+ * units, hide columns). Syncs with PropsUi so options dialog and toolbar
stay in sync.
+ */
+ private void addMetricsViewDropdown(Control toolbarControl) {
+ if (!(toolbarControl instanceof ToolBar tb)) {
+ return;
+ }
+ ToolItem viewItem = new ToolItem(tb, SWT.DROP_DOWN);
+ viewItem.setText(BaseMessages.getString(PKG,
"PipelineLog.MetricsView.View"));
+ viewItem.setToolTipText(BaseMessages.getString(PKG,
"PipelineLog.MetricsView.View.Tooltip"));
+
+ viewItem.addListener(
+ SWT.Selection,
+ e -> {
+ Menu menu = createMetricsViewMenu(tb);
+ Point point;
+ if (e.detail == SWT.ARROW) {
+ point = tb.toDisplay(e.x, e.y + viewItem.getBounds().height);
+ } else {
+ // Clicked the "View" label: show menu below the button
+ Rectangle bounds = viewItem.getBounds();
+ point = tb.toDisplay(bounds.x, bounds.y + bounds.height);
+ }
+ menu.setLocation(point.x, point.y);
+ menu.setVisible(true);
+ });
+ }
+
+ private Menu createMetricsViewMenu(ToolBar parent) {
+ Menu menu = new Menu(parent.getShell(), SWT.POP_UP);
+ PropsUi props = PropsUi.getInstance();
+
+ MenuItem showUnitsItem = new MenuItem(menu, SWT.CHECK);
+ showUnitsItem.setText(BaseMessages.getString(PKG,
"PipelineLog.MetricsView.ShowUnits"));
+ showUnitsItem.setSelection(props.isMetricsPanelShowUnits());
+ showUnitsItem.addListener(
+ SWT.Selection,
+ e ->
+ setMetricsOptionAndRefresh(
+ () ->
props.setMetricsPanelShowUnits(showUnitsItem.getSelection())));
+
+ new MenuItem(menu, SWT.SEPARATOR);
+
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowInput",
+ props.isMetricsPanelShowInput(),
+ props::setMetricsPanelShowInput);
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowRead",
+ props.isMetricsPanelShowRead(),
+ props::setMetricsPanelShowRead);
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowOutput",
+ props.isMetricsPanelShowOutput(),
+ props::setMetricsPanelShowOutput);
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowUpdated",
+ props.isMetricsPanelShowUpdated(),
+ props::setMetricsPanelShowUpdated);
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowRejected",
+ props.isMetricsPanelShowRejected(),
+ props::setMetricsPanelShowRejected);
+ addMetricsViewMenuItem(
+ menu,
+ "PipelineLog.MetricsView.ShowBuffersInput",
+ props.isMetricsPanelShowBuffersInput(),
+ props::setMetricsPanelShowBuffersInput);
+
+ return menu;
+ }
+
+ private void addMetricsViewMenuItem(
+ Menu menu, String messageKey, boolean selected, Consumer<Boolean>
setOption) {
+ MenuItem item = new MenuItem(menu, SWT.CHECK);
+ item.setText(BaseMessages.getString(PKG, messageKey));
+ item.setSelection(selected);
+ item.addListener(
+ SWT.Selection,
+ e -> setMetricsOptionAndRefresh(() ->
setOption.accept(item.getSelection())));
+ }
+
+ private void setMetricsOptionAndRefresh(Runnable setOption) {
+ setOption.run();
+ try {
+ HopConfig.getInstance().saveToFile();
+ } catch (Exception ignored) {
+ // best-effort persist
+ }
+ refreshView();
+ }
+
/**
* Add the transform-name search bar to the toolbar. Layout differs for
ToolBar (desktop) vs
* Composite (web flow).
@@ -509,10 +637,11 @@ public class HopGuiPipelineGridDelegate {
getShownComponents(engineMetrics, selectedTransformNames);
List<IEngineMetric> usedMetrics = getUsedMetrics(engineMetrics);
List<ColumnInfo> columns = buildColumnList(usedMetrics);
+ applySavedColumnWidthsToColumnInfos(columns, usedMetrics);
List<List<String>> componentStringsList =
buildComponentStringsList(engineMetrics, shownComponents,
usedMetrics);
- recreateTableIfColumnsChanged(columns, shownComponents.size());
+ recreateTableIfColumnsChanged(columns, shownComponents.size(),
usedMetrics);
sortComponentStringsByColumn(componentStringsList, gridSortColumn,
gridSortDescending);
@@ -521,7 +650,11 @@ public class HopGuiPipelineGridDelegate {
setSortIndicator();
+ // Ignore resize events from optWidth and restoreColumnWidths so we
don't overwrite saved
+ // widths
+ restoringColumnWidths = true;
pipelineGridView.optWidth(true);
+ restoreColumnWidths(usedMetrics);
previousRefreshColumns = columns;
updateEditButtonState();
} finally {
@@ -583,11 +716,29 @@ public class HopGuiPipelineGridDelegate {
private List<IEngineMetric> getUsedMetrics(EngineMetrics engineMetrics) {
List<IEngineMetric> usedMetrics = new
ArrayList<>(engineMetrics.getMetricsList());
+ usedMetrics.removeIf(this::isMetricHidden);
Collections.sort(
usedMetrics, (o1, o2) ->
o1.getDisplayPriority().compareTo(o2.getDisplayPriority()));
return usedMetrics;
}
+ private boolean isMetricHidden(IEngineMetric metric) {
+ String code = metric.getCode();
+ if (code == null) {
+ return false;
+ }
+ PropsUi props = PropsUi.getInstance();
+ return switch (code) {
+ case Pipeline.METRIC_NAME_INPUT -> !props.isMetricsPanelShowInput();
+ case Pipeline.METRIC_NAME_READ -> !props.isMetricsPanelShowRead();
+ case Pipeline.METRIC_NAME_OUTPUT -> !props.isMetricsPanelShowOutput();
+ case Pipeline.METRIC_NAME_UPDATED -> !props.isMetricsPanelShowUpdated();
+ case Pipeline.METRIC_NAME_REJECTED ->
!props.isMetricsPanelShowRejected();
+ case Pipeline.METRIC_NAME_BUFFER_IN ->
!props.isMetricsPanelShowBuffersInput();
+ default -> false;
+ };
+ }
+
private List<ColumnInfo> buildColumnList(List<IEngineMetric> usedMetrics) {
List<ColumnInfo> columns = new ArrayList<>();
@@ -609,7 +760,11 @@ public class HopGuiPipelineGridDelegate {
for (IEngineMetric metric : usedMetrics) {
ColumnInfo column =
- new ColumnInfo(metric.getHeader(), ColumnInfo.COLUMN_TYPE_TEXT,
metric.isNumeric(), true);
+ new ColumnInfo(
+ PipelineMetricDisplayUtil.getDisplayHeaderWithUnit(metric),
+ ColumnInfo.COLUMN_TYPE_TEXT,
+ metric.isNumeric(),
+ true);
column.setToolTip(metric.getTooltip());
IValueMeta stringMeta = new ValueMetaString(metric.getCode());
ValueMetaInteger valueMeta = new ValueMetaInteger(metric.getCode(), 15,
0);
@@ -655,6 +810,22 @@ public class HopGuiPipelineGridDelegate {
return columns;
}
+ /**
+ * Applies saved column widths to ColumnInfo so that TableView.optWidth()
will use them instead of
+ * auto-sizing. Table column 0 is the "#" column (no ColumnInfo); indices
1..n match our columns.
+ */
+ private void applySavedColumnWidthsToColumnInfos(
+ List<ColumnInfo> columns, List<IEngineMetric> usedMetrics) {
+ List<String> keys = getColumnKeys(usedMetrics);
+ for (int i = 0; i < columns.size() && (i + 1) < keys.size(); i++) {
+ String key = keys.get(i + 1);
+ Integer w = metricsColumnWidths.get(key);
+ if (w != null && w > 0) {
+ columns.get(i).setWidth(w);
+ }
+ }
+ }
+
private List<List<String>> buildComponentStringsList(
EngineMetrics engineMetrics,
List<IEngineComponent> shownComponents,
@@ -667,12 +838,26 @@ public class HopGuiPipelineGridDelegate {
componentStrings.add(Const.NVL(component.getName(), ""));
componentStrings.add(Integer.toString(component.getCopyNr()));
+ boolean showUnits = PropsUi.getInstance().isMetricsPanelShowUnits();
for (IEngineMetric metric : usedMetrics) {
Long value = engineMetrics.getComponentMetric(component, metric);
- componentStrings.add(value == null ? "" : formatMetric(value));
+ String unit = showUnits ?
PipelineMetricDisplayUtil.getUnitForMetricCell(metric) : null;
+ String cell =
+ value == null
+ ? ""
+ : formatMetric(value) + (unit != null && !unit.isEmpty() ? " "
+ unit : "");
+ componentStrings.add(cell);
}
- componentStrings.add(calculateDuration(component));
-
componentStrings.add(Const.NVL(engineMetrics.getComponentSpeedMap().get(component),
""));
+ String durationStr = calculateDuration(component);
+ componentStrings.add(
+ durationStr.isEmpty() ? "" : showUnits ? durationStr + " (h:m:s)" :
durationStr);
+ String speedStr = engineMetrics.getComponentSpeedMap().get(component);
+ componentStrings.add(
+ speedStr == null || speedStr.isEmpty()
+ ? ""
+ : showUnits && !speedStr.trim().equals("-")
+ ? speedStr.trim() + " rows/s"
+ : speedStr.trim());
componentStrings.add(Const.NVL(engineMetrics.getComponentStatusMap().get(component),
""));
componentStringsList.add(componentStrings);
@@ -680,10 +865,12 @@ public class HopGuiPipelineGridDelegate {
return componentStringsList;
}
- private void recreateTableIfColumnsChanged(List<ColumnInfo> columns, int
rowCount) {
+ private void recreateTableIfColumnsChanged(
+ List<ColumnInfo> columns, int rowCount, List<IEngineMetric> usedMetrics)
{
if (!haveColumnsChanged(columns)) {
return;
}
+ saveColumnWidths();
pipelineGridView.dispose();
pipelineGridView =
new TableView(
@@ -700,6 +887,8 @@ public class HopGuiPipelineGridDelegate {
false,
false); // no TableView toolbar; copy/filter are on our toolbar
pipelineGridView.setSortable(true);
+ metricsColumnKeys = getColumnKeys(usedMetrics);
+ attachColumnWidthListeners();
attachMetricsTableListeners(pipelineGridView);
FormData fdView = new FormData();
fdView.left = new FormAttachment(0, 0);
@@ -708,6 +897,103 @@ public class HopGuiPipelineGridDelegate {
fdView.bottom = new FormAttachment(100, 0);
pipelineGridView.setLayoutData(fdView);
pipelineGridComposite.layout(true, true);
+ restoreColumnWidths(usedMetrics);
+ }
+
+ /**
+ * Builds the list of column keys in table order (#, TransformName, Copy,
metric codes, duration,
+ * speed, status).
+ */
+ private List<String> getColumnKeys(List<IEngineMetric> usedMetrics) {
+ List<String> keys = new ArrayList<>();
+ keys.add("#");
+ keys.add("TransformName");
+ keys.add("Copy");
+ for (IEngineMetric m : usedMetrics) {
+ keys.add(m.getCode());
+ }
+ keys.add("duration");
+ keys.add("speed");
+ keys.add("status");
+ return keys;
+ }
+
+ /** Saves current column widths to the session map so they can be restored
after refresh. */
+ private void saveColumnWidths() {
+ if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+ return;
+ }
+ if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+ return;
+ }
+ org.eclipse.swt.widgets.Table table = pipelineGridView.table;
+ int n = Math.min(table.getColumnCount(), metricsColumnKeys.size());
+ for (int i = 0; i < n; i++) {
+ int w = table.getColumn(i).getWidth();
+ if (w > 0) {
+ metricsColumnWidths.put(metricsColumnKeys.get(i), w);
+ }
+ }
+ }
+
+ /**
+ * Restores column widths from the session map onto the table. Needed for
the "#" column (no
+ * ColumnInfo) and as a fallback so widths are applied after optWidth.
+ */
+ private void restoreColumnWidths(List<IEngineMetric> usedMetrics) {
+ if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+ return;
+ }
+ restoringColumnWidths = true;
+ try {
+ List<String> keys = getColumnKeys(usedMetrics);
+ Table table = pipelineGridView.table;
+ int n = Math.min(table.getColumnCount(), keys.size());
+ for (int i = 0; i < n; i++) {
+ String key = keys.get(i);
+ Integer w = metricsColumnWidths.get(key);
+ if (w != null && w > 0) {
+ TableColumn tc = table.getColumn(i);
+ tc.setWidth(w);
+ }
+ }
+ } finally {
+ // Clear flag asynchronously so any Resize events queued by setWidth()
are still ignored
+ Display display =
+ pipelineGridView != null && !pipelineGridView.isDisposed()
+ ? pipelineGridView.table.getDisplay()
+ : null;
+ if (display != null && !display.isDisposed()) {
+ display.asyncExec(() -> restoringColumnWidths = false);
+ } else {
+ restoringColumnWidths = false;
+ }
+ }
+ }
+
+ /** Attach resize listeners so user column resizes are stored for the
session. */
+ private void attachColumnWidthListeners() {
+ if (pipelineGridView == null || pipelineGridView.isDisposed()) {
+ return;
+ }
+ if (metricsColumnKeys == null || metricsColumnKeys.size() == 0) {
+ return;
+ }
+ Table table = pipelineGridView.table;
+ for (int i = 0; i < table.getColumnCount() && i <
metricsColumnKeys.size(); i++) {
+ final String key = metricsColumnKeys.get(i);
+ TableColumn col = table.getColumn(i);
+ col.addListener(
+ SWT.Resize,
+ e -> {
+ if (restoringColumnWidths) {
+ return;
+ }
+ if (!col.isDisposed()) {
+ metricsColumnWidths.put(key, col.getWidth());
+ }
+ });
+ }
}
private void fillTableRows(List<List<String>> componentStringsList, int
errorColumnIndex) {
@@ -716,7 +1002,6 @@ public class HopGuiPipelineGridDelegate {
}
int errorsCol = 3 + errorColumnIndex; // row has #, name, copy, then
metrics
Color errorBg = GuiResource.getInstance().getColorLightRed();
- Color white = GuiResource.getInstance().getColorWhite();
for (int row = 0; row < componentStringsList.size(); row++) {
List<String> componentStrings = componentStringsList.get(row);
TableItem item;
@@ -730,7 +1015,7 @@ public class HopGuiPipelineGridDelegate {
}
if (errorColumnIndex >= 0 && errorsCol < componentStrings.size()) {
long err = parseFormattedLong(componentStrings.get(errorsCol));
- item.setBackground(err > 0 ? errorBg : white);
+ item.setBackground(err > 0 ? errorBg : null);
}
}
}
@@ -897,6 +1182,9 @@ public class HopGuiPipelineGridDelegate {
if (trimmed.isEmpty()) {
return 0L;
}
+ // Strip trailing unit suffix so we can parse the number (e.g. "1,234
rows" or "1,234 r" ->
+ // 1234)
+ trimmed = trimmed.replaceAll("\\s+(rows|r|runs|flushes|rows/s)$", "");
return Long.parseLong(trimmed.replace(",", ""));
}
@@ -969,7 +1257,7 @@ public class HopGuiPipelineGridDelegate {
if (baseTransform.getErrors() > 0) {
row.setBackground(GuiResource.getInstance().getColorLightRed());
} else {
- row.setBackground(GuiResource.getInstance().getColorWhite());
+ row.setBackground(null);
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
index 8aab5e4b41..c593a8d58b 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
@@ -51,6 +51,7 @@ import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ExpandBar;
@@ -95,6 +96,13 @@ public class ConfigGuiOptionsTab {
private Button wDisableZoomScrolling;
private Button wHideMenuBar;
private Button wShowTableViewToolbar;
+ private Button wMetricsPanelShowUnits;
+ private Button wMetricsPanelShowInput;
+ private Button wMetricsPanelShowRead;
+ private Button wMetricsPanelShowOutput;
+ private Button wMetricsPanelShowUpdated;
+ private Button wMetricsPanelShowRejected;
+ private Button wMetricsPanelShowBuffersInput;
private Combo wDefaultLocale;
private boolean isReloading = false; // Flag to prevent saving during reload
@@ -165,6 +173,15 @@ public class ConfigGuiOptionsTab {
wEnableInfiniteMove.setSelection(props.isInfiniteCanvasMoveEnabled());
wHideMenuBar.setSelection(props.isHidingMenuBar());
wShowTableViewToolbar.setSelection(props.isShowTableViewToolbar());
+ if (wMetricsPanelShowUnits != null &&
!wMetricsPanelShowUnits.isDisposed()) {
+ wMetricsPanelShowUnits.setSelection(props.isMetricsPanelShowUnits());
+ wMetricsPanelShowInput.setSelection(props.isMetricsPanelShowInput());
+ wMetricsPanelShowRead.setSelection(props.isMetricsPanelShowRead());
+ wMetricsPanelShowOutput.setSelection(props.isMetricsPanelShowOutput());
+
wMetricsPanelShowUpdated.setSelection(props.isMetricsPanelShowUpdated());
+
wMetricsPanelShowRejected.setSelection(props.isMetricsPanelShowRejected());
+
wMetricsPanelShowBuffersInput.setSelection(props.isMetricsPanelShowBuffersInput());
+ }
// On macOS (and other non-Windows), dark mode follows system; sync from
system so UI and
// props match. In Web environment, isSystemDarkTheme() is not available.
boolean darkMode;
@@ -727,6 +744,128 @@ public class ConfigGuiOptionsTab {
}
}));
+ lastControl = tablesExpandBar;
+
+ // Metrics panel section - using ExpandBar
+ ExpandBar metricsPanelExpandBar = new ExpandBar(wLookComp, SWT.V_SCROLL);
+ PropsUi.setLook(metricsPanelExpandBar);
+
+ FormData fdMetricsPanelExpandBar = new FormData();
+ fdMetricsPanelExpandBar.left = new FormAttachment(0, 0);
+ fdMetricsPanelExpandBar.right = new FormAttachment(100, 0);
+ fdMetricsPanelExpandBar.top = new FormAttachment(lastControl, 2 * margin);
+ metricsPanelExpandBar.setLayoutData(fdMetricsPanelExpandBar);
+
+ Composite metricsPanelContent = new Composite(metricsPanelExpandBar,
SWT.NONE);
+ PropsUi.setLook(metricsPanelContent);
+ FormLayout metricsPanelLayout = new FormLayout();
+ metricsPanelLayout.marginWidth = PropsUi.getFormMargin();
+ metricsPanelLayout.marginHeight = PropsUi.getFormMargin();
+ metricsPanelContent.setLayout(metricsPanelLayout);
+
+ Control lastMetricsPanelControl = null;
+ wMetricsPanelShowUnits =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowUnits.Label",
+ "EnterOptionsDialog.MetricsPanel.ShowUnits.ToolTip",
+ props.isMetricsPanelShowUnits(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowUnits;
+
+ wMetricsPanelShowInput =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowInput.Label",
+ null,
+ props.isMetricsPanelShowInput(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowInput;
+
+ wMetricsPanelShowRead =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowRead.Label",
+ null,
+ props.isMetricsPanelShowRead(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowRead;
+
+ wMetricsPanelShowOutput =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowOutput.Label",
+ null,
+ props.isMetricsPanelShowOutput(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowOutput;
+
+ wMetricsPanelShowUpdated =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowUpdated.Label",
+ null,
+ props.isMetricsPanelShowUpdated(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowUpdated;
+
+ wMetricsPanelShowRejected =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowRejected.Label",
+ null,
+ props.isMetricsPanelShowRejected(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowRejected;
+
+ wMetricsPanelShowBuffersInput =
+ createCheckbox(
+ metricsPanelContent,
+ "EnterOptionsDialog.MetricsPanel.ShowBuffersInput.Label",
+ null,
+ props.isMetricsPanelShowBuffersInput(),
+ lastMetricsPanelControl,
+ margin);
+ lastMetricsPanelControl = wMetricsPanelShowBuffersInput;
+
+ ExpandItem metricsPanelItem = new ExpandItem(metricsPanelExpandBar,
SWT.NONE);
+ metricsPanelItem.setText(
+ BaseMessages.getString(PKG,
"EnterOptionsDialog.Section.MetricsPanel"));
+ metricsPanelItem.setControl(metricsPanelContent);
+ metricsPanelItem.setHeight(metricsPanelContent.computeSize(SWT.DEFAULT,
SWT.DEFAULT).y);
+ metricsPanelItem.setExpanded(true);
+
+ metricsPanelExpandBar.addListener(
+ SWT.Expand,
+ e ->
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ if (!wLookComp.isDisposed() && !sLookComp.isDisposed()) {
+ wLookComp.layout();
+
sLookComp.setMinHeight(wLookComp.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
+ }
+ }));
+ metricsPanelExpandBar.addListener(
+ SWT.Collapse,
+ e ->
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ if (!wLookComp.isDisposed() && !sLookComp.isDisposed()) {
+ wLookComp.layout();
+
sLookComp.setMinHeight(wLookComp.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
+ }
+ }));
+
+ lastControl = metricsPanelExpandBar;
+
FormData fdLookComp = new FormData();
fdLookComp.left = new FormAttachment(0, 0);
fdLookComp.right = new FormAttachment(100, 0);
@@ -957,6 +1096,13 @@ public class ConfigGuiOptionsTab {
props.setDarkMode(darkMode);
props.setHidingMenuBar(wHideMenuBar.getSelection());
props.setShowTableViewToolbar(wShowTableViewToolbar.getSelection());
+ props.setMetricsPanelShowUnits(wMetricsPanelShowUnits.getSelection());
+ props.setMetricsPanelShowInput(wMetricsPanelShowInput.getSelection());
+ props.setMetricsPanelShowRead(wMetricsPanelShowRead.getSelection());
+ props.setMetricsPanelShowOutput(wMetricsPanelShowOutput.getSelection());
+ props.setMetricsPanelShowUpdated(wMetricsPanelShowUpdated.getSelection());
+
props.setMetricsPanelShowRejected(wMetricsPanelShowRejected.getSelection());
+
props.setMetricsPanelShowBuffersInput(wMetricsPanelShowBuffersInput.getSelection());
int defaultLocaleIndex = wDefaultLocale.getSelectionIndex();
if (defaultLocaleIndex < 0 || defaultLocaleIndex >=
GlobalMessages.localeCodes.length) {
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
index 7b8e874e99..37b3039b66 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/PipelineExecutionViewer.java
@@ -83,6 +83,7 @@ import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.HopGuiExtensionPoint;
import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.file.pipeline.PipelineMetricDisplayUtil;
import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
import org.apache.hop.ui.hopgui.shared.BaseExecutionViewer;
import org.apache.hop.ui.hopgui.shared.CanvasZoomHelper;
@@ -516,8 +517,13 @@ public class PipelineExecutionViewer extends
BaseExecutionViewer
Set<String> metricNames,
IEngineMetric metric) {
if (metricNames.contains(metric.getHeader())) {
- columns.add(new ColumnInfo(metric.getHeader(),
ColumnInfo.COLUMN_TYPE_TEXT, true, true));
- // Index +1 because of the left-hand row number
+ columns.add(
+ new ColumnInfo(
+ PipelineMetricDisplayUtil.getDisplayHeaderWithUnit(metric),
+ ColumnInfo.COLUMN_TYPE_TEXT,
+ true,
+ true));
+ // Index +1 because of the left-hand row number; use raw header for
lookup
indexMap.put(metric.getHeader(), columns.size());
}
}
diff --git
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
index d6ac80b3c6..d424e9ca57 100644
---
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
@@ -88,6 +88,15 @@ EnterOptionsDialog.Section.Appearance=Appearance
EnterOptionsDialog.Section.GeneralAppearance=General appearance
EnterOptionsDialog.Section.PipelineWorkflowCanvas=Pipeline and Workflow canvas
EnterOptionsDialog.Section.TablesGrids=Tables and grids
+EnterOptionsDialog.Section.MetricsPanel=Metrics panel
+EnterOptionsDialog.MetricsPanel.ShowUnits.Label=Show units in cells
+EnterOptionsDialog.MetricsPanel.ShowUnits.ToolTip=When enabled, units (e.g. r,
h:m:s, rows/s) are shown in the pipeline metrics grid cells.
+EnterOptionsDialog.MetricsPanel.ShowInput.Label=Show Input
+EnterOptionsDialog.MetricsPanel.ShowRead.Label=Show Read
+EnterOptionsDialog.MetricsPanel.ShowOutput.Label=Show Output
+EnterOptionsDialog.MetricsPanel.ShowUpdated.Label=Show Updated
+EnterOptionsDialog.MetricsPanel.ShowRejected.Label=Show Rejected
+EnterOptionsDialog.MetricsPanel.ShowBuffersInput.Label=Show Buffers Input
EnterOptionsDialog.SplitHopsConfirm.Label=Show confirmation when splitting hops
EnterOptionsDialog.SplitHopsConfirm.Tooltip=If a transform is drawn on a hop,
the hop can be split so that the new transform lays between the two original
ones.\nIf this option is enabled, a confirmation dialog will be shown before
splitting.
EnterOptionsDialog.SaveConfirm.Label=Show confirmation to save file when
starting pipeline or workflow
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
index dd6117e962..009a82b344 100644
---
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
@@ -227,23 +227,34 @@ PipelineLog.Button.ShowErrorLines=\ &Show error lines
PipelineLog.Button.ShowOnlyActiveTransforms=Hide inactive
PipelineLog.Button.ShowOnlySelectedTransforms=Show only selected transforms
PipelineLog.Search.TransformName.Placeholder=Filter by transform name...
+PipelineLog.MetricsView.View=View
+PipelineLog.MetricsView.View.Tooltip=Show or hide columns and units in the
metrics grid
+PipelineLog.MetricsView.ShowUnits=Show units in cells
+PipelineLog.MetricsView.ShowInput=Show Input
+PipelineLog.MetricsView.ShowRead=Show Read
+PipelineLog.MetricsView.ShowOutput=Show Output
+PipelineLog.MetricsView.ShowUpdated=Show Updated
+PipelineLog.MetricsView.ShowRejected=Show Rejected
+PipelineLog.MetricsView.ShowBuffersInput=Show Buffers Input
PipelineLog.Column.Active=Active
-PipelineLog.Column.BuffersInput=Buffers Input
-PipelineLog.Column.BuffersOutput=Buffers Output
+PipelineLog.Column.BuffersInput=Buffers Input (rows)
+PipelineLog.Column.BuffersOutput=Buffers Output (rows)
PipelineLog.Column.Copynr=Copy
-PipelineLog.Column.Duration=Duration
-PipelineLog.Column.Errors=Errors
-PipelineLog.Column.Input=Input
-PipelineLog.Column.Output=Output
+PipelineLog.Column.Duration=Duration (h:m:s)
+PipelineLog.Column.DurationNoUnit=Duration
+PipelineLog.Column.SpeedNoUnit=Speed
+PipelineLog.Column.Errors=Errors (rows)
+PipelineLog.Column.Input=Input (rows)
+PipelineLog.Column.Output=Output (rows)
PipelineLog.Column.Status=Status
PipelineLog.Column.PriorityBufferSizes=input/output
-PipelineLog.Column.Read=Read
-PipelineLog.Column.Rejected=Rejected
-PipelineLog.Column.Speed=Speed
+PipelineLog.Column.Read=Read (rows)
+PipelineLog.Column.Rejected=Rejected (rows)
+PipelineLog.Column.Speed=Speed (rows/s)
PipelineLog.Column.Time=Time
PipelineLog.Column.TransformName=Transform Name
-PipelineLog.Column.Updated=Updated
-PipelineLog.Column.Written=Written
+PipelineLog.Column.Updated=Updated (rows)
+PipelineLog.Column.Written=Written (rows)
PipelineLog.Dialog.DoNoPreviewWhileRunning.Message=Sorry, it is not possible
to preview transforms when the pipeline is running.
PipelineLog.Dialog.DoNoPreviewWhileRunning.Title=Sorry
PipelineLog.Dialog.DoNoStartPipelineTwice.Message=This pipeline is already
running, and must complete before it can run again.