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 4fb4efe623 Issue 6498 (#6688)
4fb4efe623 is described below
commit 4fb4efe62307980baa1cd1f628f4a74fc5b69289
Author: Matt Casters <[email protected]>
AuthorDate: Fri Mar 6 14:10:10 2026 +0100
Issue 6498 (#6688)
* issue #6498
* issue #6498 (tweak)
* issue #6498 (basics)
* issue #6498 (c)
* issue #6498 (time filter and selector push-down optimization)
* issue #6498 (Neo4j and OpenSearch optimizations)
* issue #6498 (Performance optimizations, small UI improvements)
* issue #6498 (default history to 1 hour)
---
.../core/gui/plugin/toolbar/GuiToolbarElement.java | 7 +
.../gui/plugin/toolbar/GuiToolbarElementType.java | 1 +
.../core/gui/plugin/toolbar/GuiToolbarItem.java | 2 +
.../org/apache/hop/core/metrics/MetricsUtil.java | 28 ++
.../hop/execution/DefaultExecutionSelector.java | 99 ++++
.../org/apache/hop/execution/ExecutionState.java | 30 ++
.../apache/hop/execution/IExecutionDateFilter.java | 15 +-
.../hop/execution/IExecutionInfoLocation.java | 16 +-
.../apache/hop/execution/IExecutionSelector.java | 53 +++
.../java/org/apache/hop/execution/LastPeriod.java | 125 +++++
.../caching/BaseCachingExecutionInfoLocation.java | 70 ++-
.../apache/hop/execution/caching/CacheEntry.java | 5 +
.../caching/CachingFileExecutionInfoLocation.java | 41 +-
.../execution/local/FileExecutionInfoLocation.java | 12 +
.../remote/RemoteExecutionInfoLocation.java | 12 +
.../java/org/apache/hop/history/AuditState.java | 49 +-
.../java/org/apache/hop/history/AuditStateMap.java | 21 +-
.../hop/pipeline/anon/AnonymousPipelineRunner.java | 2 +
.../apache/hop/projects/gui/ProjectsGuiPlugin.java | 13 +-
.../elastic/ElasticExecutionInfoLocation.java | 4 +-
.../neo4j/execution/NeoExecutionInfoLocation.java | 214 +++++++--
.../neo4j/execution/builder/BaseCypherBuilder.java | 4 +
.../execution/builder/CypherQueryBuilder.java | 46 +-
.../neo4j/execution/cache/NeoLocationCache.java | 175 +++++++
.../OpenSearchExecutionInfoLocation.java | 155 ++++---
.../org/apache/hop/ui/core/gui/GuiResource.java | 60 +++
.../apache/hop/ui/core/gui/GuiToolbarWidgets.java | 51 +++
.../main/java/org/apache/hop/ui/hopgui/HopGui.java | 1 +
.../ui/hopgui/delegates/HopGuiFileDelegate.java | 6 +
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 20 +-
.../hopgui/file/workflow/HopGuiWorkflowGraph.java | 19 +-
.../execution/ExecutionPerspective.java | 506 +++++++++++++++++++--
.../execution/PipelineExecutionViewer.java | 24 +-
.../execution/WorkflowExecutionViewer.java | 19 +-
.../execution/messages/messages_en_US.properties | 25 +
.../resources/ui/images/finished-icon-disabled.svg | 17 +
ui/src/main/resources/ui/images/finished-icon.svg | 18 +
ui/src/main/resources/ui/images/force-refresh.svg | 16 +
.../main/resources/ui/images/pipeline-disabled.svg | 5 +
ui/src/main/resources/ui/images/run-light.svg | 11 +
.../resources/ui/images/running-icon-disabled.svg | 16 +
ui/src/main/resources/ui/images/running-icon.svg | 16 +
ui/src/main/resources/ui/images/up-disabled.svg | 4 +
.../main/resources/ui/images/workflow-disabled.svg | 4 +
44 files changed, 1813 insertions(+), 224 deletions(-)
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElement.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElement.java
index 2fa8c91c00..93ec6d30cf 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElement.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElement.java
@@ -140,4 +140,11 @@ public @interface GuiToolbarElement {
* @return if the widget should be read-only
*/
boolean readOnly() default false;
+
+ /**
+ * Set the default content for a TEXT widget in the toolbar.
+ *
+ * @return The default text value
+ */
+ String defaultText() default "";
}
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
index 0aed8972e3..b0e7a1bf9d 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
@@ -22,6 +22,7 @@ public enum GuiToolbarElementType {
BUTTON,
LABEL,
COMBO,
+ TEXT,
CHECKBOX,
;
}
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarItem.java
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarItem.java
index 4ed1c7687d..e14aaed30d 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarItem.java
+++
b/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarItem.java
@@ -39,6 +39,7 @@ public class GuiToolbarItem extends BaseGuiElements
implements Comparable<GuiToo
private String getComboValuesMethod;
private boolean ignored;
private boolean addingSeparator;
+ private String defaultText;
private ClassLoader classLoader;
// The singleton listener class to use
@@ -85,6 +86,7 @@ public class GuiToolbarItem extends BaseGuiElements
implements Comparable<GuiToo
this.extraWidth = toolbarElement.extraWidth();
this.alignRight = toolbarElement.alignRight();
this.readOnly = toolbarElement.readOnly();
+ this.defaultText = toolbarElement.defaultText();
}
@Override
diff --git a/core/src/main/java/org/apache/hop/core/metrics/MetricsUtil.java
b/core/src/main/java/org/apache/hop/core/metrics/MetricsUtil.java
index 03b36682db..06598b34bb 100644
--- a/core/src/main/java/org/apache/hop/core/metrics/MetricsUtil.java
+++ b/core/src/main/java/org/apache/hop/core/metrics/MetricsUtil.java
@@ -95,6 +95,34 @@ public class MetricsUtil {
return new ArrayList<>(map.values());
}
+ public static MetricsDuration getLastDuration(String logChannelId, String
metricsCode) {
+ Queue<IMetricsSnapshot> metrics =
MetricsRegistry.getInstance().getSnapshotList(logChannelId);
+ Iterator<IMetricsSnapshot> iterator = metrics.iterator();
+ IMetricsSnapshot lastStartSnapshot = null;
+ IMetricsSnapshot lastStopSnapshot = null;
+ while (iterator.hasNext()) {
+ IMetricsSnapshot snapshot = iterator.next();
+ if (snapshot.getMetric().getCode().equals(metricsCode)) {
+ if (snapshot.getMetric().getType() == MetricsSnapshotType.START) {
+ lastStartSnapshot = snapshot;
+ }
+ if (snapshot.getMetric().getType() == MetricsSnapshotType.STOP) {
+ lastStopSnapshot = snapshot;
+ }
+ }
+ }
+ if (lastStartSnapshot != null && lastStopSnapshot != null) {
+ long ms = lastStopSnapshot.getDate().getTime() -
lastStartSnapshot.getDate().getTime();
+ return new MetricsDuration(
+ lastStartSnapshot.getDate(),
+ lastStartSnapshot.getMetric().getDescription(),
+ lastStartSnapshot.getSubject(),
+ logChannelId,
+ ms);
+ }
+ return null;
+ }
+
public static List<MetricsDuration> getAllDurations(String
parentLogChannelId) {
List<MetricsDuration> durations = new ArrayList<>();
diff --git
a/engine/src/main/java/org/apache/hop/execution/DefaultExecutionSelector.java
b/engine/src/main/java/org/apache/hop/execution/DefaultExecutionSelector.java
new file mode 100644
index 0000000000..d6c2554bc0
--- /dev/null
+++
b/engine/src/main/java/org/apache/hop/execution/DefaultExecutionSelector.java
@@ -0,0 +1,99 @@
+/*
+ * 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.execution;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hop.core.exception.HopException;
+
+public record DefaultExecutionSelector(
+ boolean isSelectingParents,
+ boolean isSelectingFailed,
+ boolean isSelectingRunning,
+ boolean isSelectingFinished,
+ boolean isSelectingWorkflows,
+ boolean isSelectingPipelines,
+ String filterText,
+ LastPeriod startDateFilter)
+ implements IExecutionSelector {
+ public static final SimpleDateFormat START_DATE_FORMAT = new
SimpleDateFormat("yyyy/MM/dd HH:mm");
+
+ public boolean isSelected(Execution execution) {
+ if (isSelectingWorkflows &&
!execution.getExecutionType().equals(ExecutionType.Workflow)) {
+ return false;
+ }
+ if (isSelectingPipelines &&
!execution.getExecutionType().equals(ExecutionType.Pipeline)) {
+ return false;
+ }
+ if (StringUtils.isNotEmpty(filterText)) {
+ boolean match =
execution.getName().toLowerCase().contains(filterText.toLowerCase());
+ match = match || execution.getId().contains(filterText);
+ if (execution.getExecutionStartDate() != null) {
+ String startDateString =
START_DATE_FORMAT.format(execution.getExecutionStartDate());
+ match = match || startDateString.contains(filterText);
+ }
+ if (!match) {
+ return false;
+ }
+ }
+ if (startDateFilter != null && execution.getExecutionStartDate() != null) {
+ return startDateFilter.matches(execution.getExecutionStartDate());
+ }
+ return false;
+ }
+
+ public boolean isSelected(ExecutionState executionState) {
+ if (isSelectingParents &&
StringUtils.isNotEmpty(executionState.getParentId())) {
+ return false;
+ }
+ if (isSelectingFailed && !executionState.isFailed()) {
+ return false;
+ }
+ if (isSelectingFinished
+ &&
!executionState.getStatusDescription().toLowerCase().startsWith("finished")) {
+ return false;
+ }
+ return !isSelectingRunning
+ ||
executionState.getStatusDescription().toLowerCase().startsWith("running");
+ }
+
+ /**
+ * This is a default implementation to make all implementations work.
+ *
+ * @param location The location to load from
+ * @param selector The selector to use
+ * @return The list of selected execution IDs.
+ * @throws HopException In case something went wrong.
+ */
+ public static List<String> findExecutionIDs(
+ IExecutionInfoLocation location, IExecutionSelector selector) throws
HopException {
+
+ List<String> selection = new ArrayList<>();
+ List<String> ids = location.getExecutionIds(false, 1000);
+ for (String id : ids) {
+ Execution execution = location.getExecution(id);
+ ExecutionState state = location.getExecutionState(id);
+ if (selector.isSelected(execution) && selector.isSelected(state)) {
+ selection.add(id);
+ }
+ }
+ return selection;
+ }
+}
diff --git a/engine/src/main/java/org/apache/hop/execution/ExecutionState.java
b/engine/src/main/java/org/apache/hop/execution/ExecutionState.java
index 05b0ea4114..f340bb6f0f 100644
--- a/engine/src/main/java/org/apache/hop/execution/ExecutionState.java
+++ b/engine/src/main/java/org/apache/hop/execution/ExecutionState.java
@@ -90,4 +90,34 @@ public class ExecutionState {
this.childIds = new ArrayList<>();
this.details = new HashMap<>();
}
+
+ public boolean isRunning() {
+ if (statusDescription == null) {
+ return false;
+ }
+ return (statusDescription.toLowerCase().contains("running"))
+ || statusDescription.toLowerCase().contains("initializing");
+ }
+
+ public boolean isFinished() {
+ return executionEndDate != null;
+ }
+
+ /**
+ * We haven't received an update in quite a while
+ *
+ * @return true if the state is stale
+ */
+ public boolean isStale(long loggingInterval) {
+ if (isFinished()) {
+ // After finishing updates are no longer received.
+ return false;
+ }
+ if (updateTime == null) {
+ // We didn't get an update yet right after starting.
+ // It's too early to call it stale.
+ return false;
+ }
+ return System.currentTimeMillis() - updateTime.getTime() > loggingInterval;
+ }
}
diff --git
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
b/engine/src/main/java/org/apache/hop/execution/IExecutionDateFilter.java
similarity index 77%
copy from
core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
copy to engine/src/main/java/org/apache/hop/execution/IExecutionDateFilter.java
index 0aed8972e3..47434c6826 100644
---
a/core/src/main/java/org/apache/hop/core/gui/plugin/toolbar/GuiToolbarElementType.java
+++ b/engine/src/main/java/org/apache/hop/execution/IExecutionDateFilter.java
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -15,13 +15,10 @@
* limitations under the License.
*/
-package org.apache.hop.core.gui.plugin.toolbar;
+package org.apache.hop.execution;
-public enum GuiToolbarElementType {
- NONE, // To disable default options
- BUTTON,
- LABEL,
- COMBO,
- CHECKBOX,
- ;
+import java.util.Date;
+
+public interface IExecutionDateFilter {
+ boolean isChosenDate(Date executionStartDate);
}
diff --git
a/engine/src/main/java/org/apache/hop/execution/IExecutionInfoLocation.java
b/engine/src/main/java/org/apache/hop/execution/IExecutionInfoLocation.java
index f8ee73df07..43df73daa5 100644
--- a/engine/src/main/java/org/apache/hop/execution/IExecutionInfoLocation.java
+++ b/engine/src/main/java/org/apache/hop/execution/IExecutionInfoLocation.java
@@ -58,10 +58,13 @@ public interface IExecutionInfoLocation extends Cloneable {
* When you're done with this location you can call this method to clean up
any left-over
* temporary files, memory structures or database connections.
*
- * @throws HopException
+ * @throws HopException in case the underlying storage had an issue
*/
void close() throws HopException;
+ /** Clear any caching to force a full refresh of execution information from
source locations. */
+ void clearCaches();
+
/**
* Remove any buffering or caching for the execution information with the
given ID.
*
@@ -186,6 +189,17 @@ public interface IExecutionInfoLocation extends Cloneable {
*/
List<Execution> findExecutions(IExecutionMatcher matcher) throws
HopException;
+ /**
+ * Find execution IDs of pipelines and workflows. This method accepts a
selector which will allow
+ * implementations to be more efficient with the retrieval of execution IDs.
For example, it would
+ * be possible to look at states first to limit the list of IDs.
+ *
+ * @param selector The selection condition to apply to all available
executions
+ * @return The list of selected execution IDs.
+ * @throws HopException In case something goes wrong
+ */
+ List<String> findExecutionIDs(IExecutionSelector selector) throws
HopException;
+
/**
* Get execution data for transforms or an action. The parent ID would
typically be a pipeline ID,
* and you'd get data for all the transforms. You can also get the execution
data for specific
diff --git
a/engine/src/main/java/org/apache/hop/execution/IExecutionSelector.java
b/engine/src/main/java/org/apache/hop/execution/IExecutionSelector.java
new file mode 100644
index 0000000000..e2a739ba33
--- /dev/null
+++ b/engine/src/main/java/org/apache/hop/execution/IExecutionSelector.java
@@ -0,0 +1,53 @@
+/*
+ * 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.execution;
+
+public interface IExecutionSelector {
+ boolean isSelectingParents();
+
+ boolean isSelectingFailed();
+
+ boolean isSelectingRunning();
+
+ boolean isSelectingFinished();
+
+ boolean isSelectingPipelines();
+
+ boolean isSelectingWorkflows();
+
+ String filterText();
+
+ LastPeriod startDateFilter();
+
+ /**
+ * We can filter on the level of the Execution here, for example on name and
type.
+ *
+ * @param execution The execution to filter on
+ * @return true if the execution gets selected using the selector parameters.
+ */
+ boolean isSelected(Execution execution);
+
+ /**
+ * Here we filter using the execution state, for example with the status
description, the failure
+ * state and the start date.
+ *
+ * @param executionState The Execution State to evaluate
+ * @return true if the execution state gets selected using the selector
parameters.
+ */
+ boolean isSelected(ExecutionState executionState);
+}
diff --git a/engine/src/main/java/org/apache/hop/execution/LastPeriod.java
b/engine/src/main/java/org/apache/hop/execution/LastPeriod.java
new file mode 100644
index 0000000000..b2ed69478c
--- /dev/null
+++ b/engine/src/main/java/org/apache/hop/execution/LastPeriod.java
@@ -0,0 +1,125 @@
+/*
+ * 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.execution;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.util.Date;
+import lombok.Getter;
+import org.apache.hop.metadata.api.IEnumHasCodeAndDescription;
+
+@Getter
+public enum LastPeriod implements IEnumHasCodeAndDescription {
+ NONE("-"),
+ FIVE_MINUTES("< 5m"),
+ FIFTEEN_MINUTES("< 15m"),
+ THIRTY_MINUTES("< 30m"),
+ ONE_HOUR("< 1h"),
+ TWO_HOURS("< 2h"),
+ FOUR_HOURS("< 4h"),
+ SIX_HOURS("< 6h"),
+ TWELVE_HOURS("< 12h"),
+ ONE_DAY("< 1d"),
+ TWO_DAYS("< 2d"),
+ FOUR_DAYS("< 4d"),
+ ONE_WEEK("< 1w"),
+ TWO_WEEKS("< 2w"),
+ ONE_MONTH("< 1M"),
+ TWO_MONTHS("< 2M"),
+ THREE_MONTHS("< 3M"),
+ SIX_MONTHS("< 6M"),
+ ONE_YEAR("< 1Y"),
+ ;
+ private final String description;
+
+ private LastPeriod(String description) {
+ this.description = description;
+ }
+
+ public String getCode() {
+ return name();
+ }
+
+ public static LastPeriod lookupDescription(String description) {
+ for (LastPeriod lastPeriod : values()) {
+ // This is a case sensitive equals() to see the difference
+ // between Months and minutes.
+ //
+ if (lastPeriod.description.equals(description)) {
+ return lastPeriod;
+ }
+ }
+ return ONE_HOUR;
+ }
+
+ public boolean matches(Date date) {
+ LocalDateTime pastTime =
date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+ LocalDateTime now = LocalDateTime.now();
+
+ return switch (this) {
+ case FIVE_MINUTES -> Duration.between(pastTime, now).toMinutes() <= 5;
+ case FIFTEEN_MINUTES -> Duration.between(pastTime, now).toMinutes() <=
15;
+ case THIRTY_MINUTES -> Duration.between(pastTime, now).toMinutes() <= 30;
+ case ONE_HOUR -> Duration.between(pastTime, now).toHours() <= 1;
+ case TWO_HOURS -> Duration.between(pastTime, now).toHours() <= 2;
+ case FOUR_HOURS -> Duration.between(pastTime, now).toHours() <= 4;
+ case SIX_HOURS -> Duration.between(pastTime, now).toHours() <= 6;
+ case TWELVE_HOURS -> Duration.between(pastTime, now).toHours() <= 12;
+ case ONE_DAY -> Duration.between(pastTime, now).toDays() <= 1;
+ case TWO_DAYS -> Duration.between(pastTime, now).toDays() <= 2;
+ case FOUR_DAYS -> Duration.between(pastTime, now).toDays() <= 4;
+ case ONE_WEEK -> Duration.between(pastTime, now).toDays() <= 7;
+ case TWO_WEEKS -> Duration.between(pastTime, now).toDays() <= 14;
+ case ONE_MONTH -> Period.between(pastTime.toLocalDate(),
now.toLocalDate()).getMonths() <= 1;
+ case TWO_MONTHS -> Period.between(pastTime.toLocalDate(),
now.toLocalDate()).getMonths() <= 2;
+ case THREE_MONTHS ->
+ Period.between(pastTime.toLocalDate(),
now.toLocalDate()).getMonths() <= 3;
+ case SIX_MONTHS -> Period.between(pastTime.toLocalDate(),
now.toLocalDate()).getMonths() <= 6;
+ case ONE_YEAR -> Period.between(pastTime.toLocalDate(),
now.toLocalDate()).getYears() <= 1;
+ default -> true;
+ };
+ }
+
+ public LocalDateTime calculateStartDate() {
+ LocalDateTime now = LocalDateTime.now();
+
+ return switch (this) {
+ case FIVE_MINUTES -> now.minusMinutes(5);
+ case FIFTEEN_MINUTES -> now.minusMinutes(15);
+ case THIRTY_MINUTES -> now.minusMinutes(30);
+ case ONE_HOUR -> now.minusHours(1);
+ case TWO_HOURS -> now.minusHours(2);
+ case FOUR_HOURS -> now.minusHours(4);
+ case SIX_HOURS -> now.minusHours(6);
+ case TWELVE_HOURS -> now.minusHours(12);
+ case ONE_DAY -> now.minusDays(1);
+ case TWO_DAYS -> now.minusDays(2);
+ case FOUR_DAYS -> now.minusDays(4);
+ case ONE_WEEK -> now.minusDays(7);
+ case TWO_WEEKS -> now.minusDays(14);
+ case ONE_MONTH -> now.minusMonths(1);
+ case TWO_MONTHS -> now.minusMonths(2);
+ case THREE_MONTHS -> now.minusMonths(3);
+ case SIX_MONTHS -> now.minusMonths(6);
+ case ONE_YEAR -> now.minusYears(1);
+ default -> LocalDateTime.of(1900, 1, 1, 0, 0);
+ };
+ }
+}
diff --git
a/engine/src/main/java/org/apache/hop/execution/caching/BaseCachingExecutionInfoLocation.java
b/engine/src/main/java/org/apache/hop/execution/caching/BaseCachingExecutionInfoLocation.java
index 8ab57fbb12..b6b7bc6838 100644
---
a/engine/src/main/java/org/apache/hop/execution/caching/BaseCachingExecutionInfoLocation.java
+++
b/engine/src/main/java/org/apache/hop/execution/caching/BaseCachingExecutionInfoLocation.java
@@ -48,6 +48,7 @@ import org.apache.hop.execution.ExecutionState;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
import org.apache.hop.execution.IExecutionMatcher;
+import org.apache.hop.execution.IExecutionSelector;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
@@ -82,7 +83,7 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
protected Timer cacheTimer;
- protected AtomicBoolean locked;
+ protected final AtomicBoolean locked;
protected int delay;
protected int maxAge;
@@ -111,7 +112,8 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
protected abstract void deleteCacheEntry(CacheEntry cacheEntry) throws
HopException;
- protected abstract void retrieveIds(boolean includeChildren, Set<DatedId>
ids, int limit)
+ protected abstract void retrieveIds(
+ boolean includeChildren, Set<DatedId> ids, int limit, IExecutionSelector
selector)
throws HopException;
@Override
@@ -195,6 +197,13 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
}
}
+ @Override
+ public void clearCaches() {
+ synchronized (locked) {
+ cache.clear();
+ }
+ }
+
@Override
public synchronized void registerExecution(Execution execution) throws
HopException {
/*
@@ -220,7 +229,7 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
// Get all the IDs from disk if we don't have it in the cache.
//
- retrieveIds(includeChildren, ids, limit);
+ retrieveIds(includeChildren, ids, limit, null);
// Reverse sort the IDs by date
//
@@ -244,6 +253,34 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
return list;
}
+ @Override
+ public List<String> findExecutionIDs(IExecutionSelector selector) throws
HopException {
+ Set<DatedId> dateIds = new HashSet<>();
+
+ // The data in the cache is the most recent, so we start with that.
+ //
+ getExecutionIdsFromCache(dateIds, selector);
+
+ // Get all the IDs from disk if we don't have it in the cache.
+ //
+ retrieveIds(!selector.isSelectingParents(), dateIds, 50, selector);
+
+ // Reverse sort the IDs by date
+ //
+ List<DatedId> datedIds = new ArrayList<>(dateIds);
+ datedIds.sort(Comparator.comparing(DatedId::getDate));
+ Collections.reverse(datedIds); // Newest first
+
+ // Take only the first from the list
+ //
+ int iLimit = datedIds.size();
+ List<String> list = new ArrayList<>();
+ for (DatedId datedId : datedIds) {
+ list.add(datedId.getId());
+ }
+ return list;
+ }
+
/**
* Add the execution to the cache as a top level object.
*
@@ -435,6 +472,21 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
}
}
+ protected static void addChildIds(
+ CacheEntry entry, Set<DatedId> ids, IExecutionSelector selector) {
+ for (String childId : entry.getChildIds()) {
+ Execution childExecution = entry.getChildExecution(childId);
+ if (!selector.isSelected(childExecution)) {
+ continue;
+ }
+ ExecutionState childExecutionState =
entry.getChildExecutionState(childId);
+ if (!selector.isSelected(childExecutionState)) {
+ continue;
+ }
+ ids.add(new DatedId(childExecution.getId(),
childExecution.getRegistrationDate()));
+ }
+ }
+
protected void getExecutionIdsFromCache(Set<DatedId> ids, boolean
includeChildren) {
for (CacheEntry cacheEntry : cache.values()) {
ids.add(new DatedId(cacheEntry.getId(),
cacheEntry.getExecution().getRegistrationDate()));
@@ -444,6 +496,18 @@ public abstract class BaseCachingExecutionInfoLocation
implements IExecutionInfo
}
}
+ protected void getExecutionIdsFromCache(Set<DatedId> ids, IExecutionSelector
selector) {
+ for (CacheEntry cacheEntry : cache.values()) {
+ if (selector.isSelected(cacheEntry.getExecution())
+ && selector.isSelected(cacheEntry.getExecutionState())) {
+ ids.add(new DatedId(cacheEntry.getId(),
cacheEntry.getExecution().getRegistrationDate()));
+ }
+ if (!selector.isSelectingParents()) {
+ addChildIds(cacheEntry, ids, selector);
+ }
+ }
+ }
+
@Override
public Execution getExecution(String executionId) throws HopException {
CacheEntry entry = findCacheEntry(executionId);
diff --git
a/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
b/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
index 668ba37696..d720993a55 100644
--- a/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
+++ b/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
@@ -50,6 +50,10 @@ public class CacheEntry {
// The name of the pipeline of workflow
private String name;
+ // The creation date of this entry
+ //
+ private Date creationDate;
+
// The parent execution: pipeline or workflow
private Execution execution;
@@ -79,6 +83,7 @@ public class CacheEntry {
childExecutionData = new HashMap<>();
summary = new EntrySummary();
lastWritten = new Date();
+ creationDate = new Date();
dirty = true;
}
diff --git
a/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
b/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
index 08bb4a9f14..2120533906 100644
---
a/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
+++
b/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
@@ -19,6 +19,9 @@
package org.apache.hop.execution.caching;
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Set;
import lombok.Getter;
@@ -36,6 +39,8 @@ import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.execution.ExecutionInfoLocation;
import org.apache.hop.execution.IExecutionInfoLocation;
+import org.apache.hop.execution.IExecutionSelector;
+import org.apache.hop.execution.LastPeriod;
import org.apache.hop.execution.plugin.ExecutionInfoLocationPlugin;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
@@ -129,7 +134,8 @@ public class CachingFileExecutionInfoLocation extends
BaseCachingExecutionInfoLo
}
@Override
- protected void retrieveIds(boolean includeChildren, Set<DatedId> ids, int
limit)
+ protected void retrieveIds(
+ boolean includeChildren, Set<DatedId> ids, int limit, final
IExecutionSelector selector)
throws HopException {
try {
FileObject[] files = getAllFileObjects(actualRootFolder);
@@ -137,14 +143,43 @@ public class CachingFileExecutionInfoLocation extends
BaseCachingExecutionInfoLo
for (FileObject file : files) {
String id = getIdFromFileName(file);
+ // We do a first filtering on the modification date
+ // This is probably the execution end date, so we add an hour.
+ //
+ LastPeriod dateFilter = selector.startDateFilter();
+ if (dateFilter != null) {
+ LocalDateTime roughStartDate =
dateFilter.calculateStartDate().minusHours(1);
+ long startDate =
+ ZonedDateTime.of(roughStartDate,
ZoneId.systemDefault()).toInstant().toEpochMilli();
+ long lastModified = file.getContent().getLastModifiedTime();
+ if (lastModified < startDate) {
+ // Skip for performance
+ continue;
+ }
+ }
+
+ // Apply the other filters by loading the file
+ //
+ CacheEntry entry = findCacheEntry(id);
+ if (entry == null) {
+ // Not much loaded from disk or cache
+ continue;
+ }
+ if (!selector.isSelected(entry.getExecution())) {
+ continue;
+ }
+ if (!selector.isSelected(entry.getExecutionState())) {
+ continue;
+ }
+
if (!ids.contains(new DatedId(id, null))) {
ids.add(new DatedId(id, new
Date(file.getContent().getLastModifiedTime())));
// To add child IDs we need to load the file.
// We won't store these in the cache though.
//
- if (includeChildren) {
- CacheEntry entry = loadCacheEntry(id);
+ if (!selector.isSelectingParents()) {
+ entry = loadCacheEntry(id);
if (entry != null) {
addChildIds(entry, ids);
}
diff --git
a/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
b/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
index da09786f8e..f92a301f26 100644
---
a/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
+++
b/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
@@ -41,6 +41,7 @@ import org.apache.hop.core.gui.plugin.GuiWidgetElement;
import org.apache.hop.core.json.HopJson;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.execution.DefaultExecutionSelector;
import org.apache.hop.execution.Execution;
import org.apache.hop.execution.ExecutionData;
import org.apache.hop.execution.ExecutionInfoLocation;
@@ -48,6 +49,7 @@ import org.apache.hop.execution.ExecutionState;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
import org.apache.hop.execution.IExecutionMatcher;
+import org.apache.hop.execution.IExecutionSelector;
import org.apache.hop.execution.plugin.ExecutionInfoLocationPlugin;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
@@ -111,6 +113,11 @@ public class FileExecutionInfoLocation implements
IExecutionInfoLocation {
// Nothing to close
}
+ @Override
+ public void clearCaches() {
+ // Nothing to clear
+ }
+
@Override
public void unBuffer(String executionId) throws HopException {
// Nothing to remove from a buffer or cache
@@ -494,6 +501,11 @@ public class FileExecutionInfoLocation implements
IExecutionInfoLocation {
}
}
+ @Override
+ public List<String> findExecutionIDs(IExecutionSelector pruner) throws
HopException {
+ return DefaultExecutionSelector.findExecutionIDs(this, pruner);
+ }
+
@Override
public synchronized List<Execution> findExecutions(IExecutionMatcher matcher)
throws HopException {
diff --git
a/engine/src/main/java/org/apache/hop/execution/remote/RemoteExecutionInfoLocation.java
b/engine/src/main/java/org/apache/hop/execution/remote/RemoteExecutionInfoLocation.java
index 6138f75462..c116a19b89 100644
---
a/engine/src/main/java/org/apache/hop/execution/remote/RemoteExecutionInfoLocation.java
+++
b/engine/src/main/java/org/apache/hop/execution/remote/RemoteExecutionInfoLocation.java
@@ -33,6 +33,7 @@ import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.gui.plugin.GuiWidgetElement;
import org.apache.hop.core.json.HopJson;
import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.execution.DefaultExecutionSelector;
import org.apache.hop.execution.Execution;
import org.apache.hop.execution.ExecutionData;
import org.apache.hop.execution.ExecutionInfoLocation;
@@ -40,6 +41,7 @@ import org.apache.hop.execution.ExecutionState;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
import org.apache.hop.execution.IExecutionMatcher;
+import org.apache.hop.execution.IExecutionSelector;
import org.apache.hop.execution.plugin.ExecutionInfoLocationPlugin;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
@@ -122,6 +124,11 @@ public class RemoteExecutionInfoLocation implements
IExecutionInfoLocation {
@Override
public void close() throws HopException {}
+ @Override
+ public void clearCaches() {
+ // Nothing to cache. We think the Hop server services are not the slowest
part.
+ }
+
@Override
public void unBuffer(String executionId) throws HopException {}
@@ -235,6 +242,11 @@ public class RemoteExecutionInfoLocation implements
IExecutionInfoLocation {
}
}
+ @Override
+ public List<String> findExecutionIDs(IExecutionSelector pruner) throws
HopException {
+ return DefaultExecutionSelector.findExecutionIDs(this, pruner);
+ }
+
@Override
public Execution getExecution(String executionId) throws HopException {
try {
diff --git a/engine/src/main/java/org/apache/hop/history/AuditState.java
b/engine/src/main/java/org/apache/hop/history/AuditState.java
index cebb46abcc..5dec386ac7 100644
--- a/engine/src/main/java/org/apache/hop/history/AuditState.java
+++ b/engine/src/main/java/org/apache/hop/history/AuditState.java
@@ -19,10 +19,13 @@ package org.apache.hop.history;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import lombok.Getter;
+import lombok.Setter;
/** This class allows you to describe the state of objects like loaded files,
windows and so on */
+@Setter
+@Getter
public class AuditState {
-
// The name of the parent (filename, window name, ...)
private String name;
@@ -56,35 +59,27 @@ public class AuditState {
return Objects.hash(name);
}
- /**
- * Gets name
- *
- * @return value of name
- */
- public String getName() {
- return name;
- }
-
- /**
- * @param name The name to set
- */
- public void setName(String name) {
- this.name = name;
+ public boolean extractBoolean(String key, boolean defaultValue) {
+ Object only = getStateMap().get(key);
+ if (only instanceof Boolean) {
+ return (Boolean) only;
+ }
+ return defaultValue;
}
- /**
- * Gets stateMap
- *
- * @return value of stateMap
- */
- public Map<String, Object> getStateMap() {
- return stateMap;
+ public String extractString(String key, String defaultValue) {
+ Object object = getStateMap().get(key);
+ if (object instanceof String) {
+ return (String) object;
+ }
+ return defaultValue;
}
- /**
- * @param stateMap The stateMap to set
- */
- public void setStateMap(Map<String, Object> stateMap) {
- this.stateMap = stateMap;
+ public int extractInteger(String key, int defaultValue) {
+ Object object = getStateMap().get(key);
+ if (object instanceof Integer) {
+ return (Integer) object;
+ }
+ return defaultValue;
}
}
diff --git a/engine/src/main/java/org/apache/hop/history/AuditStateMap.java
b/engine/src/main/java/org/apache/hop/history/AuditStateMap.java
index e4cb145edc..96ab82171a 100644
--- a/engine/src/main/java/org/apache/hop/history/AuditStateMap.java
+++ b/engine/src/main/java/org/apache/hop/history/AuditStateMap.java
@@ -18,8 +18,13 @@ package org.apache.hop.history;
import java.util.HashMap;
import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+@Setter
+@Getter
public class AuditStateMap {
+
private Map<String, AuditState> nameStateMap;
public AuditStateMap() {
@@ -30,22 +35,6 @@ public class AuditStateMap {
this.nameStateMap = nameStateMap;
}
- /**
- * Gets nameStateMap
- *
- * @return value of nameStateMap
- */
- public Map<String, AuditState> getNameStateMap() {
- return nameStateMap;
- }
-
- /**
- * @param nameStateMap The nameStateMap to set
- */
- public void setNameStateMap(Map<String, AuditState> nameStateMap) {
- this.nameStateMap = nameStateMap;
- }
-
public void add(AuditState auditState) {
AuditState existing = get(auditState.getName());
if (existing != null) {
diff --git
a/engine/src/main/java/org/apache/hop/pipeline/anon/AnonymousPipelineRunner.java
b/engine/src/main/java/org/apache/hop/pipeline/anon/AnonymousPipelineRunner.java
index 603d4f0afa..7c80b68ac7 100644
---
a/engine/src/main/java/org/apache/hop/pipeline/anon/AnonymousPipelineRunner.java
+++
b/engine/src/main/java/org/apache/hop/pipeline/anon/AnonymousPipelineRunner.java
@@ -25,6 +25,7 @@ import lombok.Getter;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.hop.core.Const;
import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogLevel;
import org.apache.hop.core.logging.LoggingObject;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.variables.IVariables;
@@ -77,6 +78,7 @@ public class AnonymousPipelineRunner {
PipelineEngineFactory.createPipelineEngine(runConfiguration,
meta);
pipeline.setParent(new LoggingObject("AnonymousPipelineRunner"));
pipeline.setMetadataProvider(metadataProvider);
+ pipeline.setLogLevel(LogLevel.ERROR);
final AnonymousPipelineResults results = new
AnonymousPipelineResults();
diff --git
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
index d98ee62323..d1173c358d 100644
---
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
+++
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
@@ -88,6 +88,8 @@ import org.apache.hop.ui.core.gui.HopNamespace;
import org.apache.hop.ui.core.vfs.HopVfsFileDialog;
import org.apache.hop.ui.core.widget.FileTree;
import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.perspective.execution.ExecutionPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
import org.apache.hop.ui.pipeline.dialog.PipelineExecutionConfigurationDialog;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.apache.hop.workflow.config.WorkflowRunConfiguration;
@@ -170,8 +172,8 @@ public class ProjectsGuiPlugin {
// Save explorer perspective state for the current project before
switching namespace
//
-
org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective.getInstance()
- .saveExplorerStateOnShutdown();
+ ExplorerPerspective.getInstance().saveExplorerStateOnShutdown();
+ ExecutionPerspective.getInstance().saveState();
// This is called only in Hop GUI so we want to start with a new set of
variables
// It avoids variables from one project showing up in another
@@ -271,6 +273,13 @@ public class ProjectsGuiPlugin {
AuditManager.getActive().storeEvent(envUsedEvent);
}
+ // Restore the state of the execution perspective as well
+ //
+ ExecutionPerspective.getInstance().restoreState();
+ if (ExecutionPerspective.getInstance().isActive()) {
+ ExecutionPerspective.getInstance().refresh();
+ }
+
// Send out an event notifying that a new project is activated...
// The metadata has changed so fire those events as well
//
diff --git
a/plugins/tech/elastic/src/main/java/org/apache/hop/execution/elastic/ElasticExecutionInfoLocation.java
b/plugins/tech/elastic/src/main/java/org/apache/hop/execution/elastic/ElasticExecutionInfoLocation.java
index 12bece0acf..27ccdb335a 100644
---
a/plugins/tech/elastic/src/main/java/org/apache/hop/execution/elastic/ElasticExecutionInfoLocation.java
+++
b/plugins/tech/elastic/src/main/java/org/apache/hop/execution/elastic/ElasticExecutionInfoLocation.java
@@ -38,6 +38,7 @@ import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.execution.ExecutionInfoLocation;
import org.apache.hop.execution.IExecutionInfoLocation;
+import org.apache.hop.execution.IExecutionSelector;
import org.apache.hop.execution.caching.BaseCachingExecutionInfoLocation;
import org.apache.hop.execution.caching.CacheEntry;
import org.apache.hop.execution.caching.DatedId;
@@ -341,7 +342,8 @@ public class ElasticExecutionInfoLocation extends
BaseCachingExecutionInfoLocati
}
@Override
- protected void retrieveIds(boolean includeChildren, Set<DatedId> ids, int
limit)
+ protected void retrieveIds(
+ boolean includeChildren, Set<DatedId> ids, int limit, IExecutionSelector
selector)
throws HopException {
// Get all the IDs from Elastic if we don't have it in the cache.
//
diff --git
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/NeoExecutionInfoLocation.java
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/NeoExecutionInfoLocation.java
index ce0d54dfe1..6bb20bad1b 100644
---
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/NeoExecutionInfoLocation.java
+++
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/NeoExecutionInfoLocation.java
@@ -61,6 +61,8 @@ import
org.apache.hop.execution.ExecutionStateComponentMetrics;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
import org.apache.hop.execution.IExecutionMatcher;
+import org.apache.hop.execution.IExecutionSelector;
+import org.apache.hop.execution.LastPeriod;
import org.apache.hop.execution.plugin.ExecutionInfoLocationPlugin;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
@@ -74,6 +76,7 @@ import
org.apache.hop.neo4j.execution.builder.CypherMergeBuilder;
import org.apache.hop.neo4j.execution.builder.CypherQueryBuilder;
import org.apache.hop.neo4j.execution.builder.CypherRelationshipBuilder;
import org.apache.hop.neo4j.execution.builder.ICypherBuilder;
+import org.apache.hop.neo4j.execution.cache.NeoLocationCache;
import org.apache.hop.neo4j.shared.NeoConnection;
import org.apache.hop.ui.core.dialog.EnterTextDialog;
import org.apache.hop.ui.core.dialog.ErrorDialog;
@@ -82,6 +85,7 @@ import org.apache.hop.ui.hopgui.HopGui;
import
org.apache.hop.ui.hopgui.file.workflow.delegates.HopGuiWorkflowClipboardDelegate;
import org.apache.hop.workflow.action.ActionMeta;
import org.eclipse.swt.SWT;
+import org.jetbrains.annotations.NotNull;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
@@ -179,9 +183,9 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
@HopMetadataProperty(key = "connection")
protected String connectionName;
- private ILogChannel log;
- private Driver driver;
- private Session session;
+ private transient ILogChannel log;
+ private transient Driver driver;
+ private transient Session session;
public NeoExecutionInfoLocation() {}
@@ -235,6 +239,11 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
}
}
+ @Override
+ public void clearCaches() {
+ NeoLocationCache.clear();
+ }
+
@Override
public void unBuffer(String executionId) {
// There is nothing to remove from a buffer or cache.
@@ -252,6 +261,7 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
StringBuilder cypher = new StringBuilder();
addIndex(cypher, "idx_execution_id", EL_EXECUTION, EP_ID);
+ addIndex(cypher, "idx_execution_start_date", EL_EXECUTION,
EP_EXECUTION_START_DATE);
addIndex(cypher, "idx_execution_failed", EL_EXECUTION, EP_FAILED);
addIndex(cypher, "idx_execution_parent_id", EL_EXECUTION, EP_PARENT_ID);
addIndex(cypher, "idx_execution_metric_id", EL_EXECUTION, EP_ID, EP_NAME,
EP_COPY_NR);
@@ -290,18 +300,18 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
private void addIndex(StringBuilder cypher, String indexName, String label,
String... keys) {
assert keys != null && keys.length > 0 : "specify one or more keys";
- String keysClause = "ON ";
+ StringBuilder keysClause = new StringBuilder("ON ");
boolean firstKey = true;
for (String key : keys) {
if (firstKey) {
firstKey = false;
- keysClause += "( ";
+ keysClause.append("( ");
} else {
- keysClause += ", ";
+ keysClause.append(", ");
}
- keysClause += "n." + key;
+ keysClause.append("n.").append(key);
}
- keysClause += ") ";
+ keysClause.append(") ");
cypher
.append("CREATE INDEX ")
.append(indexName)
@@ -452,11 +462,15 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
execute(transaction, selfRelationshipBuilder);
}
+ // See if we need to cache this
+ //
+ NeoLocationCache.store(execution);
+
// Transaction is automatically committed by executeWrite
return true;
} catch (Exception e) {
// Transaction is automatically rolled back by executeWrite on exception
- throw e;
+ throw new RuntimeException("Error registering new Execution in Neo4j",
e);
}
}
@@ -467,6 +481,8 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
return session.executeWrite(transaction ->
deleteNeo4jExecution(transaction, executionId));
} catch (Exception e) {
throw new HopException("Error deleting execution with id " +
executionId + " in Neo4j", e);
+ } finally {
+ NeoLocationCache.remove(executionId);
}
}
}
@@ -544,6 +560,13 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
}
private Execution getNeo4jExecution(TransactionContext transaction, String
executionId) {
+ // Check the cache
+ //
+ Execution execution = NeoLocationCache.getExecution(executionId);
+ if (execution != null) {
+ return execution;
+ }
+
CypherQueryBuilder builder =
CypherQueryBuilder.of()
.withLabelAndKey("n", EL_EXECUTION, EP_ID, executionId)
@@ -569,6 +592,15 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
}
org.neo4j.driver.Record record = result.next();
+ execution = buildExecution(executionId, record);
+
+ // Add it to the cache
+ NeoLocationCache.store(execution);
+
+ return execution;
+ }
+
+ private @NotNull Execution buildExecution(String executionId,
org.neo4j.driver.Record record) {
return ExecutionBuilder.of()
.withId(executionId)
.withParentId(getString(record, EP_PARENT_ID))
@@ -620,6 +652,101 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
return ids;
}
+ @Override
+ public List<String> findExecutionIDs(IExecutionSelector selector) throws
HopException {
+ synchronized (this) {
+ try {
+ return session.executeRead(transaction ->
findNeo4jExecutionIDs(transaction, selector));
+ } catch (Exception e) {
+ throw new HopException(CONST_ERROR_GETTING_EXECUTION_FROM_NEO_4_J, e);
+ }
+ }
+ }
+
+ public List<String> findNeo4jExecutionIDs(
+ TransactionContext transaction, IExecutionSelector selector) {
+ List<String> ids = new ArrayList<>();
+
+ CypherQueryBuilder builder =
+ CypherQueryBuilder.of().withLabelAndKeys("n", EL_EXECUTION, Map.of());
+
+ // Can we push down some selector parameters?
+ //
+ boolean firstCondition = true;
+ if (selector.isSelectingParents()) {
+ builder.withWhereIsNull(firstCondition, "n", EP_PARENT_ID);
+ firstCondition = false;
+ }
+ if (selector.isSelectingFailed()) {
+ builder.withWhereEquals(firstCondition, "n", EP_FAILED, "pFailed", true);
+ firstCondition = false;
+ }
+ if (selector.isSelectingRunning()) {
+ builder.withWhereEquals(firstCondition, "n", EP_STATUS_DESCRIPTION,
"pStatus", "Running");
+ firstCondition = false;
+ }
+ if (selector.isSelectingFinished()) {
+ builder.withWhereContains(firstCondition, "n", EP_STATUS_DESCRIPTION,
"pStatus", "Finished");
+ firstCondition = false;
+ }
+ if (selector.isSelectingWorkflows()) {
+ builder.withWhereEquals(firstCondition, "n", EP_EXECUTION_TYPE, "pType",
"Workflow");
+ } else if (selector.isSelectingPipelines()) {
+ builder.withWhereEquals(firstCondition, "n", EP_EXECUTION_TYPE, "pType",
"Pipeline");
+ } else {
+ if (firstCondition) {
+ builder.withExtraClause(" WHERE ");
+ } else {
+ builder.withExtraClause(" AND ");
+ }
+ builder.withExtraClause("n." + EP_EXECUTION_TYPE + " IN [ 'Workflow',
'Pipeline' ]");
+ }
+ if (selector.startDateFilter() != LastPeriod.NONE) {
+ builder.withExtraClause(" AND n." + EP_EXECUTION_START_DATE + " >=
$fromStartDate ");
+ builder.parameters().put("fromStartDate",
selector.startDateFilter().calculateStartDate());
+ }
+
+ // The properties to return
+ builder.withReturnValues(
+ "n",
+ EP_ID,
+ EP_NAME,
+ EP_COPY_NR,
+ EP_FILENAME,
+ EP_PARENT_ID,
+ EP_EXECUTION_TYPE,
+ EP_EXECUTOR_XML,
+ EP_METADATA_JSON,
+ EP_RUN_CONFIG_NAME,
+ EP_LOG_LEVEL,
+ EP_REGISTRATION_DATE,
+ EP_EXECUTION_START_DATE,
+ EP_STATUS_DESCRIPTION,
+ EP_UPDATE_TIME,
+ EP_CHILD_IDS,
+ EP_FAILED,
+ EP_DETAILS,
+ EP_CONTAINER_ID,
+ EP_EXECUTION_END_DATE);
+
+ // ORDER BY executionStartDate DESC
+ builder.withOrderBy("n", EP_EXECUTION_START_DATE, false);
+ builder.withLimit(50);
+
+ Result result = transaction.run(builder.cypher(), builder.parameters());
+ while (result.hasNext()) {
+ org.neo4j.driver.Record record = result.next();
+ String executionId = getString(record, EP_ID);
+ Execution execution = buildExecution(executionId, record);
+ ExecutionState state = buildExecutionState(executionId, record, "");
+ if (selector.isSelected(execution) && selector.isSelected(state)) {
+ ids.add(executionId);
+ }
+ }
+
+ return ids;
+ }
+
@Override
public void updateExecutionState(ExecutionState executionState) throws
HopException {
synchronized (this) {
@@ -685,7 +812,10 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
return true;
} catch (Exception e) {
// Transaction is automatically rolled back by executeWrite on exception
- throw e;
+ throw new RuntimeException("Error updating the state of an execution in
Neo4j", e);
+ } finally {
+ // Update the cache
+ NeoLocationCache.store(state);
}
}
@@ -709,6 +839,12 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
private ExecutionState getNeo4jExecutionState(
TransactionContext transaction, String executionId, boolean
includeLogging) {
+ // Check the cache first
+ ExecutionState cachedState =
NeoLocationCache.getExecutionState(executionId);
+ if (cachedState != null) {
+ return cachedState;
+ }
+
CypherQueryBuilder executionBuilder =
CypherQueryBuilder.of()
.withLabelAndKey("n", EL_EXECUTION, EP_ID, executionId)
@@ -742,21 +878,7 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
loggingText = getString(record, EP_LOGGING_TEXT);
}
- ExecutionStateBuilder stateBuilder =
- ExecutionStateBuilder.of()
- .withId(executionId)
- .withName(getString(record, EP_NAME))
- .withCopyNr(getString(record, EP_COPY_NR))
- .withParentId(getString(record, EP_PARENT_ID))
- .withLoggingText(loggingText)
- .withExecutionType(ExecutionType.valueOf(getString(record,
EP_EXECUTION_TYPE)))
- .withStatusDescription(getString(record, EP_STATUS_DESCRIPTION))
- .withUpdateTime(getDate(record, EP_UPDATE_TIME))
- .withChildIds(getList(record, EP_CHILD_IDS))
- .withFailed(getBoolean(record, EP_FAILED))
- .withDetails(getMap(record, EP_DETAILS))
- .withContainerId(getString(record, EP_CONTAINER_ID))
- .withExecutionEndDate(getDate(record, EP_EXECUTION_END_DATE));
+ ExecutionState state = buildExecutionState(executionId, record,
loggingText);
// Add the metrics to the state...
//
@@ -789,9 +911,32 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
}
}
- stateBuilder.withMetrics(new ArrayList(metricsMap.values()));
+ state.setMetrics(new ArrayList<>(metricsMap.values()));
+
+ // Save it in the cache
+ //
+ NeoLocationCache.store(state);
- return stateBuilder.build();
+ return state;
+ }
+
+ private @NotNull ExecutionState buildExecutionState(
+ String executionId, org.neo4j.driver.Record record, String loggingText) {
+ return ExecutionStateBuilder.of()
+ .withId(executionId)
+ .withName(getString(record, EP_NAME))
+ .withCopyNr(getString(record, EP_COPY_NR))
+ .withParentId(getString(record, EP_PARENT_ID))
+ .withLoggingText(loggingText)
+ .withExecutionType(ExecutionType.valueOf(getString(record,
EP_EXECUTION_TYPE)))
+ .withStatusDescription(getString(record, EP_STATUS_DESCRIPTION))
+ .withUpdateTime(getDate(record, EP_UPDATE_TIME))
+ .withChildIds(getList(record, EP_CHILD_IDS))
+ .withFailed(getBoolean(record, EP_FAILED))
+ .withDetails(getMap(record, EP_DETAILS))
+ .withContainerId(getString(record, EP_CONTAINER_ID))
+ .withExecutionEndDate(getDate(record, EP_EXECUTION_END_DATE))
+ .build();
}
@Override
@@ -854,8 +999,8 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
*
* @param executionType The type of execution to look for
* @param name The name of the executor
- * @return
- * @throws HopException
+ * @return The previous successful execution
+ * @throws HopException In case something went wrong finding the execution
*/
@Override
public Execution findPreviousSuccessfulExecution(ExecutionType
executionType, String name)
@@ -887,8 +1032,8 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
/**
* Find those Executions that have a matching parentId
*
- * @param transaction
- * @param parentExecutionId
+ * @param transaction The read transaction to use
+ * @param parentExecutionId The parent execution ID
* @return The list of executions or an empty list if nothing was found
*/
private List<Execution> findNeo4jExecutions(
@@ -960,6 +1105,8 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
assert data != null : "no execution data provided";
assert data.getExecutionType() != null : "execution data has no type";
+ // We'll not cache this data as it can be a lot.
+
// Merge the ExecutionData node
//
CypherMergeBuilder stateCypherBuilder =
@@ -1025,7 +1172,7 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
return true;
} catch (Exception e) {
// Transaction is automatically rolled back by executeWrite on exception
- throw e;
+ throw new RuntimeException("Error registering execution data to Neo4j",
e);
}
}
@@ -1188,7 +1335,7 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
.withCreate("s", "r", R_HAS_ROW));
}
} catch (Exception e) {
- throw new RuntimeException(e);
+ throw new RuntimeException("Error saving rows and their metadata in
Neo4j", e);
}
}
@@ -1392,6 +1539,7 @@ public class NeoExecutionInfoLocation implements
IExecutionInfoLocation {
// We get the String version
// Convert to type JsonNode
//
+ //noinspection CatchMayIgnoreException
try {
yield ((ValueMetaJson)
valueMeta).convertStringToJson(value.asString());
} catch (Exception e) {
diff --git
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/BaseCypherBuilder.java
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/BaseCypherBuilder.java
index 62969f1ff1..ce0496f3f5 100644
---
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/BaseCypherBuilder.java
+++
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/BaseCypherBuilder.java
@@ -57,6 +57,10 @@ public abstract class BaseCypherBuilder implements
ICypherBuilder {
}
}
+ public void withExtraClause(String clause) {
+ cypher.append(clause).append(" ");
+ }
+
public String cypher() {
return cypher.toString();
}
diff --git
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/CypherQueryBuilder.java
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/CypherQueryBuilder.java
index 3e6a678d48..c515ba25d0 100644
---
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/CypherQueryBuilder.java
+++
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/builder/CypherQueryBuilder.java
@@ -96,7 +96,51 @@ public class CypherQueryBuilder extends BaseCypherBuilder {
}
public CypherQueryBuilder withWhereIsNull(String nodeAlias, String property)
{
- cypher.append("WHERE
").append(nodeAlias).append(".").append(property).append(" IS NULL ");
+ return withWhereIsNull(true, nodeAlias, property);
+ }
+
+ public CypherQueryBuilder withWhereIsNull(
+ boolean firstCondition, String nodeAlias, String property) {
+ addWhereOrAnd(firstCondition);
+ cypher.append(nodeAlias).append(".").append(property).append(" IS NULL ");
+ return this;
+ }
+
+ public CypherQueryBuilder withWhereEquals(
+ boolean firstCondition, String nodeAlias, String property, String
valueKey, Object value) {
+ addWhereOrAnd(firstCondition);
+ cypher
+ .append(nodeAlias)
+ .append(".")
+ .append(property)
+ .append(" = $")
+ .append(valueKey)
+ .append(" ");
+ // Add to the parameters
+ parameters.put(valueKey, value);
+ return this;
+ }
+
+ private void addWhereOrAnd(boolean firstCondition) {
+ if (firstCondition) {
+ cypher.append("WHERE ");
+ } else {
+ cypher.append(" AND ");
+ }
+ }
+
+ public CypherQueryBuilder withWhereContains(
+ boolean firstCondition, String nodeAlias, String property, String
valueKey, Object value) {
+ addWhereOrAnd(firstCondition);
+ cypher
+ .append(nodeAlias)
+ .append(".")
+ .append(property)
+ .append(" CONTAINS $")
+ .append(valueKey)
+ .append(" ");
+ // Add to the parameters
+ parameters.put(valueKey, value);
return this;
}
diff --git
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/cache/NeoLocationCache.java
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/cache/NeoLocationCache.java
new file mode 100644
index 0000000000..46201689fd
--- /dev/null
+++
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/execution/cache/NeoLocationCache.java
@@ -0,0 +1,175 @@
+/*
+ * 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.neo4j.execution.cache;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.core.Const;
+import org.apache.hop.execution.Execution;
+import org.apache.hop.execution.ExecutionData;
+import org.apache.hop.execution.ExecutionState;
+import org.apache.hop.execution.caching.CacheEntry;
+import org.apache.hop.execution.caching.DatedId;
+
+@Getter
+@Setter
+public class NeoLocationCache {
+ private static NeoLocationCache instance;
+
+ private Map<String, CacheEntry> cache;
+
+ private final AtomicBoolean locked;
+
+ private int maximumSize;
+
+ private NeoLocationCache() {
+ this.locked = new AtomicBoolean(false);
+ this.cache = new HashMap<>();
+ this.maximumSize = 1000;
+ }
+
+ public static NeoLocationCache getInstance() {
+ if (instance == null) {
+ instance = new NeoLocationCache();
+ }
+ return instance;
+ }
+
+ public static void add(CacheEntry entry) {
+ synchronized (getInstance().locked) {
+ getInstance().locked.set(true);
+ getInstance().cache.put(entry.getId(), entry);
+ getInstance().locked.set(false);
+ }
+ manageCacheSize();
+ }
+
+ public static void store(Execution execution) {
+ // Add a new Cache Entry
+ //
+ CacheEntry cacheEntry = new CacheEntry();
+ cacheEntry.setId(execution.getId());
+ cacheEntry.setExecution(execution);
+ cacheEntry.setLastWritten(new Date());
+ add(cacheEntry);
+ }
+
+ public static void store(ExecutionState executionState) {
+ // Update the cache entry
+ //
+ CacheEntry cacheEntry = get(executionState.getId());
+ if (cacheEntry != null) {
+ cacheEntry.setExecutionState(executionState);
+ cacheEntry.setLastWritten(new Date());
+ }
+ }
+
+ public static void store(String executionId, ExecutionData executionData) {
+ // Update the cache entry
+ //
+ CacheEntry cacheEntry = get(executionId);
+ if (cacheEntry != null) {
+ cacheEntry.addExecutionData(executionData);
+ cacheEntry.setLastWritten(new Date());
+ }
+ }
+
+ public static CacheEntry get(String id) {
+ synchronized (getInstance().locked) {
+ CacheEntry cacheEntry = getInstance().cache.get(id);
+ if (cacheEntry != null) {
+ cacheEntry.setLastRead(new Date());
+ }
+ return cacheEntry;
+ }
+ }
+
+ public static Execution getExecution(String executionId) {
+ CacheEntry cacheEntry = get(executionId);
+ if (cacheEntry != null) {
+ return cacheEntry.getExecution();
+ }
+ return null;
+ }
+
+ public static ExecutionState getExecutionState(String executionId) {
+ CacheEntry cacheEntry = get(executionId);
+ if (cacheEntry != null) {
+ return cacheEntry.getExecutionState();
+ }
+ return null;
+ }
+
+ public static void remove(String executionId) {
+ synchronized (getInstance().locked) {
+ getInstance().cache.remove(executionId);
+ }
+ }
+
+ private static synchronized void manageCacheSize() {
+ NeoLocationCache lc = getInstance();
+ Map<String, CacheEntry> c = lc.cache;
+ try {
+ if (lc.locked.get()) {
+ // Let the buffer overrun happen for a bit
+ // We'll sweep it clean on the next one.
+ return;
+ }
+ lc.locked.set(true);
+ // The maximum size is by default 1000 entries
+ if (c.size() >= lc.maximumSize + 50) {
+ // Remove the last 50
+ //
+ List<DatedId> datedIds = new ArrayList<>();
+ for (CacheEntry ce : c.values()) {
+ Date date = ce.getLastRead();
+ if (date == null) {
+ // Never read? Perhaps it's time to get rid of it.
+ //
+ date = Const.MIN_DATE;
+ }
+ datedIds.add(new DatedId(ce.getId(), date));
+ }
+ // reverse sort by creation date of the cache entry
+ //
+ datedIds.sort(Comparator.comparing(DatedId::getDate).reversed());
+
+ // Now delete the first 50 records in the cache
+ //
+ for (DatedId datedId : datedIds) {
+ c.remove(datedId.getId());
+ }
+ }
+ } finally {
+ instance.locked.set(false);
+ }
+ }
+
+ public static void clear() {
+ synchronized (getInstance().locked) {
+ getInstance().cache.clear();
+ }
+ }
+}
diff --git
a/plugins/tech/opensearch/src/main/java/org/apache/hop/execution/opensearch/OpenSearchExecutionInfoLocation.java
b/plugins/tech/opensearch/src/main/java/org/apache/hop/execution/opensearch/OpenSearchExecutionInfoLocation.java
index 9d4deed266..c0d4f0eebb 100644
---
a/plugins/tech/opensearch/src/main/java/org/apache/hop/execution/opensearch/OpenSearchExecutionInfoLocation.java
+++
b/plugins/tech/opensearch/src/main/java/org/apache/hop/execution/opensearch/OpenSearchExecutionInfoLocation.java
@@ -22,10 +22,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpRequest;
import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.StringUtils;
@@ -38,6 +41,8 @@ import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.execution.ExecutionInfoLocation;
import org.apache.hop.execution.IExecutionInfoLocation;
+import org.apache.hop.execution.IExecutionSelector;
+import org.apache.hop.execution.LastPeriod;
import org.apache.hop.execution.caching.BaseCachingExecutionInfoLocation;
import org.apache.hop.execution.caching.CacheEntry;
import org.apache.hop.execution.caching.DatedId;
@@ -231,7 +236,7 @@ public class OpenSearchExecutionInfoLocation extends
BaseCachingExecutionInfoLoc
// Search for the document with the given executionId
//
URI uri = URI.create(actualUrl);
- URI postUri = uri.resolve(actualIndexName + "/_search");
+ URI postUri = uri.resolve(actualIndexName + "/_search?_source=false");
String body =
"""
@@ -415,30 +420,59 @@ public class OpenSearchExecutionInfoLocation extends
BaseCachingExecutionInfoLoc
}
@Override
- protected void retrieveIds(boolean includeChildren, Set<DatedId> ids, int
limit)
+ protected void retrieveIds(
+ boolean includeChildren, Set<DatedId> ids, int limit, IExecutionSelector
selector)
throws HopException {
// Get all the IDs from OpenSearch if we don't have it in the cache.
//
try {
URI uri = URI.create(actualUrl);
- URI postUri = uri.resolve(actualIndexName + "/_search");
+ URI postUri = uri.resolve("_plugins/_sql");
String body =
"""
{
- __LIMIT_CLAUSE__
- "from": 0,
- "query" : { "match_all" : {} },
- "fields": [ "id", "execution.executionStartDate" ],
- "sort" : [ { "execution.executionStartDate" : {"order" : "desc"
}} ],
- "_source": false
+ "query": "SELECT id, creationDate __FROM_CLAUSE__
__WHERE_CLAUSE__ ORDER BY creationDate DESC LIMIT 50" }
}
""";
- String limitClause = "";
- if (limit > 0) {
- limitClause = "\"size\": " + limit + ",";
+
+ body = body.replace("__FROM_CLAUSE__", "FROM " + actualIndexName);
+
+ String whereClause = "";
+ if (selector != null) {
+ if (selector.startDateFilter() != LastPeriod.NONE) {
+ // OpenSearch uses UTC and the GUI runs in local time.
+ //
+ ZonedDateTime localStartDate =
+
selector.startDateFilter().calculateStartDate().atZone(ZoneId.systemDefault());
+ Date localStart = new
Date(localStartDate.toInstant().toEpochMilli());
+
+ SimpleDateFormat whereFormat = new SimpleDateFormat("yyyy-MM-dd
HH:mm:ss");
+ whereFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ whereClause =
+ addToWhereClause(
+ whereClause,
+ "creationDate >= datetime('" +
whereFormat.format(localStart) + "') ");
+ }
+
+ /*
+ // OpenSearch throws some errors on these nested conditions
+ //
+ if (selector.isSelectingParents()) {
+ whereClause = addToWhereClause(whereClause, "e.parentId IS NULL ");
+ }
+ if (selector.isSelectingPipelines()) {
+ whereClause = addToWhereClause(whereClause, "e.executionType =
'Pipeline' ");
+ }
+ if (selector.isSelectingWorkflows()) {
+ whereClause = addToWhereClause(whereClause, "e.executionType =
'Workflows' ");
+ }
+ if (selector.isSelectingFinished()) {
+ whereClause = addToWhereClause(whereClause, "s.statusDescription =
'Finished' ");
+ }
+ */
}
- body = body.replace("__LIMIT_CLAUSE__", limitClause);
+ body = body.replace("__WHERE_CLAUSE__", whereClause);
RestCaller restCaller =
new RestCaller(
@@ -459,43 +493,40 @@ public class OpenSearchExecutionInfoLocation extends
BaseCachingExecutionInfoLoc
//
JSONParser parser = new JSONParser();
JSONObject j = (JSONObject) parser.parse(responseBody);
- JSONObject jHitsTop = (JSONObject) j.get("hits");
- if (jHitsTop == null) {
+ JSONArray dataRows = (JSONArray) j.get("datarows");
+ if (dataRows == null) {
// Nothing left to do, something went wrong
return;
}
- JSONArray jHits = (JSONArray) jHitsTop.get("hits");
- if (Utils.isEmpty(jHits)) {
- // No hits returned
- return;
- }
// This array contains the results wrapped in its own structure.
- // The element fields.id[0] contains the result
//
- SimpleDateFormat sdf = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
- for (Object hit : jHits) {
- JSONObject jHit = (JSONObject) hit;
- JSONObject jHitsFields = (JSONObject) jHit.get("fields");
-
- JSONArray jHitsFieldsIds = (JSONArray) jHitsFields.get("id");
- if (Utils.isEmpty(jHitsFieldsIds)) {
- // Skip this one
- continue;
- }
- String id = (String) jHitsFieldsIds.get(0);
- JSONArray jHitsFieldsStart = (JSONArray)
jHitsFields.get("execution.executionStartDate");
- if (jHitsFieldsStart != null && !jHitsFieldsStart.isEmpty()) {
- String startDate = (String) jHitsFieldsStart.get(0);
- // Add the dated id
- ids.add(new DatedId(id, sdf.parse(startDate)));
- }
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ for (int r = 0; r < dataRows.size(); r++) {
+ JSONArray dataRow = (JSONArray) dataRows.get(r);
+ String id = (String) dataRow.get(0);
+ String creationDateString = (String) dataRow.get(1);
+ Date creationDate = sdf.parse(creationDateString + " UTC");
+ ids.add(new DatedId(id, creationDate));
}
} catch (Exception e) {
throw new HopException("Error finding execution ids from OpenSearch", e);
}
}
+ private String addToWhereClause(String whereClause, String clause) {
+ String result = whereClause;
+ if (StringUtils.isEmpty(whereClause)) {
+ result += " WHERE ";
+ } else {
+ result += " AND ";
+ }
+ result += clause;
+ return result;
+ }
+
/** A button to create and configure the specified index */
@GuiWidgetElement(
id = "createIndexButton",
@@ -515,20 +546,40 @@ public class OpenSearchExecutionInfoLocation extends
BaseCachingExecutionInfoLoc
{
"mappings" : {
"properties": {
- "id": { "type" : "text"},
- "name": { "type" : "text"},
- "execution.id": { "type" : "text"},
- "execution.name": { "type" : "text"},
- "execution.filename": { "type" : "text"},
- "execution.executionType": { "type" : "text"},
- "execution.parentId": { "type" : "text"},
- "execution.registrationDate": { "type": "date" },
- "execution.executionStartDate": { "type": "date" },
- "executionState.updateTime": { "type": "date" },
- "executionState.executionEndDate": { "type": "date" },
- "childExecutions": { "type": "object", "enabled" : false },
- "childExecutionStates": { "type": "object", "enabled" :
false },
- "childExecutionData": { "type": "object", "enabled" :
false }
+ "id" : { "type": "text"},
+ "name": { "type": "text"},
+ "creationDate": { "type": "date", "format":
"epoch_millis||yyyy/MM/dd HH:mm:ss.SSS||strict_date_optional_time" },
+ "summary": {
+ "type" : "nested",
+ "properties": {
+ "startDate" : { "type": "date", "format": "yyyy/MM/dd
HH:mm:ss.SSS" },
+ "endDate" : { "type": "date", "format": "yyyy/MM/dd
HH:mm:ss.SSS" },
+ "durationMs" : { "type": "long" }
+ }
+ },
+ "execution": {
+ "type" : "nested",
+ "properties": {
+ "id" : { "type": "text"} ,
+ "name" : { "type": "text" },
+ "filename" : { "type": "text" },
+ "executionType" : { "type": "text" },
+ "parentId" : { "type": "text" },
+ "registrationDate": { "type": "long" }
+ }
+ },
+ "executionState": {
+ "type" : "nested",
+ "properties": {
+ "executionStartDate": { "type": "date", "format":
"epoch_millis||yyyy/MM/dd HH:mm:ss.SSS||strict_date_optional_time" },
+ "executionEndDate": { "type": "date", "format":
"epoch_millis||yyyy/MM/dd HH:mm:ss.SSS||strict_date_optional_time" },
+ "updateTime": { "type": "date", "format":
"epoch_millis||yyyy/MM/dd HH:mm:ss.SSS||strict_date_optional_time" },
+ "statusDescription": { "type": "text" }
+ }
+ },
+ "childExecutions" : { "type": "object", "enabled":
false },
+ "childExecutionStates": { "type": "object", "enabled":
false },
+ "childExecutionData" : { "type": "object", "enabled":
false }
}
}, "settings": {
"index.mapping.total_fields.limit": 500
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
index ac839ed195..eb2f878a4f 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
@@ -144,8 +144,10 @@ public class GuiResource {
private SwtUniversalImage imageDeprecated;
private SwtUniversalImage imageVariable;
private SwtUniversalImage imagePipeline;
+ private SwtUniversalImage imagePipelineDisabled;
private SwtUniversalImage imagePartitionSchema;
private SwtUniversalImage imageWorkflow;
+ private SwtUniversalImage imageWorkflowDisabled;
private SwtUniversalImage imageArrowDefault;
private SwtUniversalImage imageArrowTrue;
private SwtUniversalImage imageArrowFalse;
@@ -162,6 +164,10 @@ public class GuiResource {
private SwtUniversalImage imageSuccess;
private SwtUniversalImage imageError;
private SwtUniversalImage imageErrorDisabled;
+ private SwtUniversalImage imageRunningIcon;
+ private SwtUniversalImage imageRunningIconDisabled;
+ private SwtUniversalImage imageFinishedIcon;
+ private SwtUniversalImage imageFinishedIconDisabled;
private SwtUniversalImage imageInfo;
private SwtUniversalImage imageInfoDisabled;
private SwtUniversalImage imageWarning;
@@ -253,6 +259,7 @@ public class GuiResource {
@Getter private Image imageUndo;
@Getter private Image imageUnselectAll;
@Getter private Image imageUp;
+ @Getter private Image imageUpDisabled;
@Getter private Image imageUser;
@Getter private Image imageView;
@@ -438,12 +445,18 @@ public class GuiResource {
imageMissing.dispose();
imageVariable.dispose();
imagePipeline.dispose();
+ imagePipelineDisabled.dispose();
imagePartitionSchema.dispose();
imageWorkflow.dispose();
+ imageWorkflowDisabled.dispose();
imageCopyRows.dispose();
imageCopyRowsDisabled.dispose();
imageError.dispose();
imageErrorDisabled.dispose();
+ imageRunningIcon.dispose();
+ imageRunningIconDisabled.dispose();
+ imageFinishedIcon.dispose();
+ imageFinishedIconDisabled.dispose();
imageInfo.dispose();
imageInfoDisabled.dispose();
imageWarning.dispose();
@@ -543,6 +556,7 @@ public class GuiResource {
disposeImage(imageUndo);
disposeImage(imageUnselectAll);
disposeImage(imageUp);
+ disposeImage(imageUpDisabled);
disposeImage(imageUser);
disposeImage(imageView);
@@ -788,6 +802,7 @@ public class GuiResource {
imageView = loadAsResource(display, "ui/images/view.svg",
ConstUi.SMALL_ICON_SIZE);
imageDown = loadAsResource(display, "ui/images/down.svg",
ConstUi.SMALL_ICON_SIZE);
imageUp = loadAsResource(display, "ui/images/up.svg",
ConstUi.SMALL_ICON_SIZE);
+ imageUpDisabled = loadAsResource(display, "ui/images/up-disabled.svg",
ConstUi.SMALL_ICON_SIZE);
imageLocation = loadAsResource(display, "ui/images/location.svg",
ConstUi.SMALL_ICON_SIZE);
imageOptions = loadAsResource(display, "ui/images/options.svg",
ConstUi.SMALL_ICON_SIZE);
imageUndo = loadAsResource(display, "ui/images/undo.svg",
ConstUi.SMALL_ICON_SIZE);
@@ -801,7 +816,11 @@ public class GuiResource {
//
imageLogo = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/logo_icon.svg");
imagePipeline = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/pipeline.svg");
+ imagePipelineDisabled =
+ SwtSvgImageUtil.getImageAsResource(display,
"ui/images/pipeline-disabled.svg");
imageWorkflow = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/workflow.svg");
+ imageWorkflowDisabled =
+ SwtSvgImageUtil.getImageAsResource(display,
"ui/images/workflow-disabled.svg");
imageServer = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/server.svg");
imagePreview = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/preview.svg");
imageTrue = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/true.svg");
@@ -828,6 +847,12 @@ public class GuiResource {
imageError = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/error.svg");
imageErrorDisabled =
SwtSvgImageUtil.getImageAsResource(display,
"ui/images/error-disabled.svg");
+ imageRunningIcon = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/running-icon.svg");
+ imageRunningIconDisabled =
+ SwtSvgImageUtil.getImageAsResource(display,
"ui/images/running-icon-disabled.svg");
+ imageFinishedIcon = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/finished-icon.svg");
+ imageFinishedIconDisabled =
+ SwtSvgImageUtil.getImageAsResource(display,
"ui/images/finished-icon-disabled.svg");
imageInfo = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/info.svg");
imageInfoDisabled = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/info-disabled.svg");
imageWarning = SwtSvgImageUtil.getImageAsResource(display,
"ui/images/warning.svg");
@@ -1168,6 +1193,11 @@ public class GuiResource {
imagePipeline, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
}
+ public Image getImagePipelineDisabled() {
+ return getZoomedImaged(
+ imagePipelineDisabled, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
@Deprecated
public Image getImageClosePanel() {
return imageClose;
@@ -1189,6 +1219,11 @@ public class GuiResource {
imageWorkflow, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
}
+ public Image getImageWorkflowDisabled() {
+ return getZoomedImaged(
+ imageWorkflowDisabled, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
/**
* @return the imageArrow
*/
@@ -1279,6 +1314,31 @@ public class GuiResource {
return getZoomedImaged(imageError, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
}
+ public Image getImageErrorDisabled() {
+ return getZoomedImaged(
+ imageErrorDisabled, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
+ public Image getImageRunningIcon() {
+ return getZoomedImaged(
+ imageRunningIcon, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
+ public Image getImageRunningIconDisabled() {
+ return getZoomedImaged(
+ imageRunningIconDisabled, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
+ public Image getImageFinishedIcon() {
+ return getZoomedImaged(
+ imageFinishedIcon, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
+ public Image getImageFinishedIconDisabled() {
+ return getZoomedImaged(
+ imageFinishedIconDisabled, display, ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE);
+ }
+
public SwtUniversalImage getSwtImageError() {
return imageError;
}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
index a9f7306cb4..5046a62eff 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
@@ -62,6 +62,7 @@ import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
@@ -223,6 +224,9 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
case CHECKBOX:
addToolbarCheckbox(toolbarItem, toolBar);
break;
+ case TEXT:
+ addToolbarText(toolbarItem, toolBar);
+ break;
default:
break;
}
@@ -258,6 +262,8 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
case CHECKBOX:
addWebToolbarCheckbox(toolbarItem, parent);
break;
+ case TEXT:
+ addWebToolbarText(toolbarItem, parent);
default:
break;
}
@@ -326,6 +332,26 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
widgetsMap.put(toolbarItem.getId(), combo);
}
+ private void addWebToolbarText(GuiToolbarItem toolbarItem, Composite parent)
{
+ Text text =
+ new Text(
+ parent,
+ SWT.SINGLE
+ | SWT.BORDER
+ | (toolbarItem.isAlignRight() ? SWT.RIGHT : SWT.LEFT)
+ | (toolbarItem.isReadOnly() ? SWT.READ_ONLY : SWT.NONE));
+ text.setText(Const.NVL(toolbarItem.getDefaultText(), ""));
+ text.setToolTipText(Const.NVL(toolbarItem.getToolTip(), ""));
+ PropsUi.setLook(text, Props.WIDGET_STYLE_TOOLBAR);
+ text.pack();
+ int width = 200 + toolbarItem.getExtraWidth();
+ text.setLayoutData(new RowData(width, SWT.DEFAULT));
+ Listener listener = getListener(toolbarItem);
+ text.addListener(SWT.Selection, listener);
+ text.addListener(SWT.DefaultSelection, listener);
+ widgetsMap.put(toolbarItem.getId(), text);
+ }
+
private void addWebToolbarCheckbox(GuiToolbarItem toolbarItem, Composite
parent) {
Button checkbox =
new Button(parent, SWT.CHECK | (toolbarItem.isAlignRight() ? SWT.RIGHT
: SWT.LEFT));
@@ -463,6 +489,31 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
PropsUi.setLook(combo, Props.WIDGET_STYLE_TOOLBAR);
}
+ private void addToolbarText(GuiToolbarItem toolbarItem, ToolBar toolBar) {
+ ToolItem textSeparator = new ToolItem(toolBar, SWT.SEPARATOR | SWT.BOTTOM);
+ Text text =
+ new Text(
+ toolBar,
+ SWT.SINGLE
+ | SWT.BORDER
+ | (toolbarItem.isAlignRight() ? SWT.RIGHT : SWT.LEFT)
+ | (toolbarItem.isReadOnly() ? SWT.READ_ONLY : SWT.NONE));
+ text.setText(Const.NVL(toolbarItem.getDefaultText(), ""));
+ text.setToolTipText(Const.NVL(toolbarItem.getToolTip(), ""));
+ PropsUi.setLook(text, Props.WIDGET_STYLE_TOOLBAR);
+ text.pack();
+ // extra room for widget decorations
+ textSeparator.setWidth(200 + toolbarItem.getExtraWidth());
+ textSeparator.setControl(text);
+
+ Listener listener = getListener(toolbarItem);
+ text.addListener(SWT.Selection, listener);
+ text.addListener(SWT.DefaultSelection, listener);
+ toolItemMap.put(toolbarItem.getId(), textSeparator);
+ widgetsMap.put(toolbarItem.getId(), text);
+ PropsUi.setLook(text, Props.WIDGET_STYLE_TOOLBAR);
+ }
+
private void addToolbarCheckbox(GuiToolbarItem toolbarItem, ToolBar toolBar)
{
ToolItem checkboxSeparator = new ToolItem(toolBar, SWT.SEPARATOR);
Button checkbox =
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index e5947a1515..b4221a6ea6 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -502,6 +502,7 @@ public class HopGui
// namespace (default or project set by extension point).
//
ExplorerPerspective.getInstance().applyRestoredState();
+ ExecutionPerspective.getInstance().restoreState();
// We need to start tracking file history again.
//
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
index a7c317fff8..806462349f 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
@@ -56,6 +56,7 @@ import
org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import org.apache.hop.ui.hopgui.file.workflow.HopGuiWorkflowGraph;
import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
import org.apache.hop.ui.hopgui.perspective.TabItemHandler;
+import org.apache.hop.ui.hopgui.perspective.execution.ExecutionPerspective;
import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
import org.apache.hop.ui.util.EnvironmentUtils;
import org.apache.hop.workflow.WorkflowMeta;
@@ -339,6 +340,11 @@ public class HopGuiFileDelegate {
explorerPerspective.saveExplorerStateOnShutdown();
}
+ ExecutionPerspective executionPerspective =
ExecutionPerspective.getInstance();
+ if (executionPerspective != null) {
+ executionPerspective.saveState();
+ }
+
return true;
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index 1912ef774c..db47d1946e 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -5607,15 +5607,17 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
variables.resolve(
pipeline.getPipelineRunConfiguration().getExecutionInfoLocationName());
if (StringUtils.isNotEmpty(locationName)) {
- ExecutionInfoLocation location =
ep.getLocationMap().get(locationName);
- IExecutionInfoLocation iLocation =
location.getExecutionInfoLocation();
- Execution execution =
iLocation.getExecution(pipeline.getLogChannelId());
- if (execution != null) {
- ExecutionState executionState =
-
location.getExecutionInfoLocation().getExecutionState(execution.getId());
- ep.createExecutionViewer(locationName, execution,
executionState);
- ep.activate();
- return;
+ ExecutionInfoLocation location = ep.lookupLocation(locationName);
+ if (location != null) {
+ IExecutionInfoLocation iLocation =
location.getExecutionInfoLocation();
+ Execution execution =
iLocation.getExecution(pipeline.getLogChannelId());
+ if (execution != null) {
+ ExecutionState executionState =
+
location.getExecutionInfoLocation().getExecutionState(execution.getId());
+ ep.createExecutionViewer(locationName, execution,
executionState);
+ ep.activate();
+ return;
+ }
}
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
index 1c4574e587..db086ab079 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
@@ -4358,15 +4358,16 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
variables.resolve(
workflow.getWorkflowRunConfiguration().getExecutionInfoLocationName());
if (StringUtils.isNotEmpty(locationName)) {
- ExecutionInfoLocation location =
- executionPerspective.getLocationMap().get(locationName);
- IExecutionInfoLocation iLocation =
location.getExecutionInfoLocation();
- Execution execution =
iLocation.getExecution(workflow.getLogChannelId());
- if (execution != null) {
- ExecutionState executionState =
iLocation.getExecutionState(execution.getId());
- executionPerspective.createExecutionViewer(locationName,
execution, executionState);
- executionPerspective.activate();
- return;
+ ExecutionInfoLocation location =
executionPerspective.lookupLocation(locationName);
+ if (location != null) {
+ IExecutionInfoLocation iLocation =
location.getExecutionInfoLocation();
+ Execution execution =
iLocation.getExecution(workflow.getLogChannelId());
+ if (execution != null) {
+ ExecutionState executionState =
iLocation.getExecutionState(execution.getId());
+ executionPerspective.createExecutionViewer(locationName,
execution, executionState);
+ executionPerspective.activate();
+ return;
+ }
}
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
index 0993e5f93b..4c21c7baec 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionPerspective.java
@@ -19,11 +19,11 @@ package org.apache.hop.ui.hopgui.perspective.execution;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import lombok.Getter;
import org.apache.hop.core.Const;
import org.apache.hop.core.Props;
import org.apache.hop.core.exception.HopException;
@@ -31,18 +31,31 @@ import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementType;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.logging.Metrics;
import org.apache.hop.core.metadata.SerializableMetadataProvider;
+import org.apache.hop.core.metrics.MetricsDuration;
+import org.apache.hop.core.metrics.MetricsSnapshotType;
+import org.apache.hop.core.metrics.MetricsUtil;
import org.apache.hop.core.search.ISearchable;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.variables.Variables;
import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.execution.DefaultExecutionSelector;
import org.apache.hop.execution.Execution;
import org.apache.hop.execution.ExecutionInfoLocation;
import org.apache.hop.execution.ExecutionState;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
+import org.apache.hop.execution.IExecutionSelector;
+import org.apache.hop.execution.LastPeriod;
+import org.apache.hop.history.AuditManager;
+import org.apache.hop.history.AuditState;
+import org.apache.hop.history.AuditStateMap;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.metadata.api.HopMetadataBase;
+import org.apache.hop.metadata.api.IEnumHasCode;
import org.apache.hop.metadata.api.IHopMetadata;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.metadata.api.IHopMetadataSerializer;
@@ -53,6 +66,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
+import org.apache.hop.ui.core.gui.HopNamespace;
import org.apache.hop.ui.core.gui.IToolbarContainer;
import org.apache.hop.ui.core.metadata.MetadataEditor;
import org.apache.hop.ui.core.metadata.MetadataManager;
@@ -80,9 +94,11 @@ import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
@@ -110,16 +126,57 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
"ExecutionPerspective-Toolbar-10030-Duplicate";
public static final String TOOLBAR_ITEM_DELETE =
"ExecutionPerspective-Toolbar-10040-Delete";
public static final String TOOLBAR_ITEM_REFRESH =
"ExecutionPerspective-Toolbar-10100-Refresh";
+ public static final String TOOLBAR_ITEM_FORCE_REFRESH =
+ "ExecutionPerspective-Toolbar-10110-ForceRefresh";
+ public static final String TOOLBAR_ITEM_SELECT_PARENTS =
+ "ExecutionPerspective-Toolbar-10150-SelectParents";
+ public static final String TOOLBAR_ITEM_SELECT_FAILED =
+ "ExecutionPerspective-Toolbar-10200-SelectFailed";
+ public static final String TOOLBAR_ITEM_SELECT_RUNNING =
+ "ExecutionPerspective-Toolbar-10300-SelectRunning";
+ public static final String TOOLBAR_ITEM_SELECT_FINISHED =
+ "ExecutionPerspective-Toolbar-10400-SelectFinished";
+ public static final String TOOLBAR_ITEM_SELECT_PIPELINES =
+ "ExecutionPerspective-Toolbar-10500-SelectPipelines";
+ public static final String TOOLBAR_ITEM_SELECT_WORKFLOWS =
+ "ExecutionPerspective-Toolbar-10600-SelectWorkflows";
+ public static final String TOOLBAR_ITEM_TIME_FILTER =
+ "ExecutionPerspective-Toolbar-10700-TimeFilter";
+ public static final String TOOLBAR_ITEM_CLEAR_FILTERS =
+ "ExecutionPerspective-Toolbar-80000-ClearFilters";
+ public static final String TOOLBAR_ITEM_FILTER_TEXT =
+ "ExecutionPerspective-Toolbar-90000-FilterText";
public static final String KEY_HELP = "Help";
public static final String CONST_ERROR = "error";
public static final String CONST_ERROR1 = "Error";
-
- private static ExecutionPerspective instance;
-
- public static ExecutionPerspective getInstance() {
- return instance;
- }
+ public static final String FILTER_NAME_DATE_ID = "name - date - ID";
+
+ private static final String EXECUTION_AUDIT_TYPE =
"execution-perspective-gui";
+ private static final String AUDIT_SASH_WEIGHTS = "sash-weights";
+ private static final String AUDIT_SASH_LEFT = "sash-left";
+ private static final String AUDIT_SASH_RIGHT = "sash-right";
+ private static final String AUDIT_EXECUTION_TOOLBAR = "toolbar";
+ private static final String AUDIT_ONLY_PARENTS = "only-parents";
+ private static final String AUDIT_ONLY_FAILED = "only-failed";
+ private static final String AUDIT_ONLY_RUNNING = "only-running";
+ private static final String AUDIT_ONLY_FINISHED = "only-finished";
+ private static final String AUDIT_ONLY_WORKFLOWS = "only-workflows";
+ private static final String AUDIT_ONLY_PIPELINES = "only-pipelines";
+ private static final String AUDIT_FILTER_TEXT = "filter-text";
+ private static final String AUDIT_TIME_FILTER = "time-filter";
+ public static final String SNAP_ID_EIL_REFRESH = "EILRefresh";
+
+ @Getter private static ExecutionPerspective instance;
+
+ private boolean onlyShowingParents = true;
+ private boolean onlyShowingFailed;
+ private boolean onlyShowingRunning;
+ private boolean onlyShowingFinished;
+ private boolean onlyShowingWorkflows;
+ private boolean onlyShowingPipelines;
+ private String filterText;
+ private LastPeriod timeFilter = LastPeriod.ONE_HOUR;
private HopGui hopGui;
private SashForm sash;
@@ -128,9 +185,16 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
private Control toolBar;
private GuiToolbarWidgets toolBarWidgets;
- private List<IExecutionViewer> viewers = new ArrayList<>();
+ private final List<IExecutionViewer> viewers = new ArrayList<>();
+
+ /**
+ * -- GETTER -- Gets locationMap
+ *
+ * @return value of locationMap
+ */
+ @Getter private Map<String, ExecutionInfoLocation> locationMap;
- private Map<String, ExecutionInfoLocation> locationMap;
+ public static final SimpleDateFormat START_DATE_FORMAT = new
SimpleDateFormat("yyyy/MM/dd HH:mm");
public ExecutionPerspective() {
instance = this;
@@ -145,7 +209,10 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
@GuiOsxKeyboardShortcut(command = true, shift = true, key = 'i', global =
true)
@Override
public void activate() {
- hopGui.setActivePerspective(this);
+ // Prevents refreshes when not needed.
+ if (!hopGui.isActivePerspective(this)) {
+ hopGui.setActivePerspective(this);
+ }
}
@Override
@@ -173,7 +240,9 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
createTree(sash);
createTabFolder(sash);
- sash.setWeights(new int[] {20, 80});
+ sash.setWeights(new int[] {30, 70});
+
+ restoreState();
this.refresh();
@@ -400,6 +469,7 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
// Load metadata
IHopMetadataProvider provider = new
SerializableMetadataProvider(execution.getMetadataJson());
IVariables variables = Variables.getADefaultVariableSpace();
+ //noinspection deprecation
variables.setVariables(execution.getVariableValues());
switch (execution.getExecutionType()) {
@@ -471,6 +541,65 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
if (hopGui == null || toolBarWidgets == null || toolBar == null ||
toolBar.isDisposed()) {
return;
}
+
+ // Update the filter icons in the toolbar
+ //
+ // Only showing parent executions
+ ToolItem item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_PARENTS);
+ if (onlyShowingParents) {
+ item.setImage(GuiResource.getInstance().getImageUp());
+ } else {
+ item.setImage(GuiResource.getInstance().getImageUpDisabled());
+ }
+
+ // Failed
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_FAILED);
+ if (onlyShowingFailed) {
+ item.setImage(GuiResource.getInstance().getImageError());
+ } else {
+ item.setImage(GuiResource.getInstance().getImageErrorDisabled());
+ }
+
+ // Running
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_RUNNING);
+ if (onlyShowingRunning) {
+ item.setImage(GuiResource.getInstance().getImageRunningIcon());
+ } else {
+ item.setImage(GuiResource.getInstance().getImageRunningIconDisabled());
+ }
+
+ // Finished
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_FINISHED);
+ if (onlyShowingFinished) {
+ item.setImage(GuiResource.getInstance().getImageFinishedIcon());
+ } else {
+ item.setImage(GuiResource.getInstance().getImageFinishedIconDisabled());
+ }
+
+ // Workflows
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_WORKFLOWS);
+ if (onlyShowingWorkflows) {
+ item.setImage(GuiResource.getInstance().getImageWorkflow());
+ } else {
+ item.setImage(GuiResource.getInstance().getImageWorkflowDisabled());
+ }
+
+ // Pipelines
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_SELECT_PIPELINES);
+ if (onlyShowingPipelines) {
+ item.setImage(GuiResource.getInstance().getImagePipeline());
+ } else {
+ item.setImage(GuiResource.getInstance().getImagePipelineDisabled());
+ }
+
+ // Time filter
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_TIME_FILTER);
+ ((Combo) item.getControl()).setText(timeFilter.getDescription());
+
+ // Filter string
+ item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_FILTER_TEXT);
+ ((Text) item.getControl()).setText(Const.NVL(filterText, ""));
+
final IHopFileTypeHandler activeHandler = getActiveFileTypeHandler();
hopGui
.getDisplay()
@@ -480,6 +609,27 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
activeHandler.getFileType(), activeHandler.hasChanged(),
false, false));
}
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_FORCE_REFRESH,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.ForceRefresh.Tooltip",
+ image = "ui/images/force-refresh.svg")
+ @GuiKeyboardShortcut(key = SWT.F5)
+ @GuiOsxKeyboardShortcut(key = SWT.F5)
+ public void forcedRefresh() {
+ // This is a manual refresh button push.
+ // As such we consider this a forced refresh.
+ // This means we'll clear the cache of the locations.
+ //
+ for (ExecutionInfoLocation location : locationMap.values()) {
+ location.getExecutionInfoLocation().clearCaches();
+ }
+
+ // Now do a refresh
+ //
+ refresh();
+ }
+
@GuiToolbarElement(
root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
id = TOOLBAR_ITEM_REFRESH,
@@ -488,6 +638,13 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
@GuiKeyboardShortcut(key = SWT.F5)
@GuiOsxKeyboardShortcut(key = SWT.F5)
public void refresh() {
+ // Only refresh if we're actually displaying anything.
+ // Let's be conservative with responsiveness.
+ //
+ if (!hopGui.isActivePerspective(this)) {
+ return;
+ }
+
Cursor busyCursor = getBusyCursor();
try {
@@ -510,9 +667,17 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
metadataProvider.getSerializer(ExecutionInfoLocation.class);
List<ExecutionInfoLocation> locations = serializer.loadAll();
- Collections.sort(locations,
Comparator.comparing(HopMetadataBase::getName));
+ locations.sort(Comparator.comparing(HopMetadataBase::getName));
+ ILogChannel log = hopGui.getLog();
+ Metrics startLocationRefresh =
+ new Metrics(MetricsSnapshotType.START, SNAP_ID_EIL_REFRESH, "Refresh
EIL tree");
+ Metrics endLocationRefresh =
+ new Metrics(MetricsSnapshotType.STOP, SNAP_ID_EIL_REFRESH, "Refresh
EIL tree end");
+
+ log.setGatheringMetrics(true);
for (ExecutionInfoLocation location : locations) {
+ log.snap(startLocationRefresh);
IExecutionInfoLocation iLocation = location.getExecutionInfoLocation();
try {
@@ -531,10 +696,21 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
locationItem.setData(location);
try {
-
- // Get the data in the location
+ // Get the data in the location. The plugins are supposed to
prune as much of the IDs
+ // upfront.
+ // Below we'll run the isSelected() condition again to make sure.
//
- List<String> ids = iLocation.getExecutionIds(false, 100);
+ IExecutionSelector executionSelector =
+ new DefaultExecutionSelector(
+ onlyShowingParents,
+ onlyShowingFailed,
+ onlyShowingRunning,
+ onlyShowingFinished,
+ onlyShowingWorkflows,
+ onlyShowingPipelines,
+ filterText,
+ timeFilter);
+ List<String> ids = iLocation.findExecutionIDs(executionSelector);
// Display the executions
//
@@ -542,13 +718,26 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
try {
Execution execution = iLocation.getExecution(id);
if (execution != null) {
+ // Apply an extra filter to make sure
+ //
+
+ if (!executionSelector.isSelected(execution)) {
+ continue;
+ }
+ // We only need to consider the state after the previous
filtering
+ //
+ ExecutionState state = iLocation.getExecutionState(id);
+ if (!executionSelector.isSelected(state)) {
+ continue;
+ }
+
TreeItem executionItem = new TreeItem(locationItem,
SWT.NONE);
switch (execution.getExecutionType()) {
case Pipeline:
- decoratePipelineTreeItem(executionItem, execution);
+ decoratePipelineTreeItem(executionItem, location,
execution, state);
break;
case Workflow:
- decorateWorkflowTreeItem(executionItem, execution);
+ decorateWorkflowTreeItem(executionItem, location,
execution, state);
break;
default:
break;
@@ -581,7 +770,19 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
locationItem.setImage(GuiResource.getInstance().getImageLocation());
locationItem.setData(CONST_ERROR, e);
}
+ log.snap(endLocationRefresh);
+ MetricsDuration duration =
+ MetricsUtil.getLastDuration(log.getLogChannelId(),
SNAP_ID_EIL_REFRESH);
+ if (duration != null) {
+ log.logBasic(
+ "Tree refresh of location "
+ + Const.rpad(location.getName(), " ", 25)
+ + " took "
+ + duration.getDuration()
+ + "ms");
+ }
}
+ log.setGatheringMetrics(false);
TreeUtil.setOptimalWidthOnColumns(tree);
TreeMemory.setExpandedFromMemory(tree, EXECUTION_PERSPECTIVE_TREE);
@@ -599,23 +800,174 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
}
}
- private void decoratePipelineTreeItem(TreeItem executionItem, Execution
execution) {
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_PARENTS,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectParents.Tooltip",
+ image = "ui/images/up.svg")
+ public void selectParents() {
+ this.onlyShowingParents = !this.onlyShowingParents;
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_FAILED,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectFailed.Tooltip",
+ image = "ui/images/error-disabled.svg")
+ public void selectFailed() {
+ this.onlyShowingFailed = !this.onlyShowingFailed;
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_RUNNING,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectRunning.Tooltip",
+ image = "ui/images/running-icon-disabled.svg")
+ public void selectRunning() {
+ this.onlyShowingRunning = !this.onlyShowingRunning;
+ this.onlyShowingFinished = false;
+ this.onlyShowingFailed = false;
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_FINISHED,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectFinished.Tooltip",
+ image = "ui/images/finished-icon-disabled.svg")
+ public void selectFinished() {
+ this.onlyShowingFinished = !this.onlyShowingFinished;
+ this.onlyShowingRunning = false;
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_PIPELINES,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectPipelines.Tooltip",
+ image = "ui/images/pipeline-disabled.svg")
+ public void selectPipelines() {
+ this.onlyShowingPipelines = !this.onlyShowingPipelines;
+ this.onlyShowingWorkflows = false;
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_SELECT_WORKFLOWS,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.SelectWorkflows.Tooltip",
+ image = "ui/images/workflow-disabled.svg")
+ public void selectWorkflows() {
+ this.onlyShowingWorkflows = !this.onlyShowingWorkflows;
+ this.onlyShowingPipelines = false;
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ public List<String> getLastPeriodDescriptions() {
+ List<String> descriptions = new ArrayList<>();
+ for (LastPeriod period : LastPeriod.values()) {
+ descriptions.add(period.getDescription());
+ }
+ return descriptions;
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_TIME_FILTER,
+ type = GuiToolbarElementType.COMBO,
+ extraWidth = -1, // make less wide
+ readOnly = true,
+ comboValuesMethod = "getLastPeriodDescriptions",
+ toolTip = "i18n::ExecutionPerspective.ToolbarElement.TimeFilter.Tooltip")
+ public void selectTimeFilter() {
+ ToolItem item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_TIME_FILTER);
+ String lastPeriodDescription = ((Combo) item.getControl()).getText();
+ this.timeFilter = LastPeriod.lookupDescription(lastPeriodDescription);
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_CLEAR_FILTERS,
+ toolTip =
"i18n::ExecutionPerspective.ToolbarElement.ClearFilters.Tooltip",
+ image = "ui/images/clear.svg")
+ public void clearFilters() {
+ this.onlyShowingParents = true;
+ this.onlyShowingPipelines = false;
+ this.onlyShowingWorkflows = false;
+ this.onlyShowingFinished = false;
+ this.onlyShowingRunning = false;
+ this.onlyShowingFailed = false;
+ this.filterText = "";
+
+ ToolItem item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_FILTER_TEXT);
+ ((Text) item.getControl()).setText(FILTER_NAME_DATE_ID);
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ @GuiToolbarElement(
+ root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+ id = TOOLBAR_ITEM_FILTER_TEXT,
+ toolTip = "i18n::ExecutionPerspective.ToolbarElement.FilterText.Tooltip",
+ type = GuiToolbarElementType.TEXT,
+ defaultText = FILTER_NAME_DATE_ID)
+ public void selectTextFilter() {
+ ToolItem item = toolBarWidgets.findToolItem(TOOLBAR_ITEM_FILTER_TEXT);
+ this.filterText = ((Text) item.getControl()).getText();
+
+ // Update the icon && apply the filter
+ updateGui();
+ refresh();
+ }
+
+ private void decoratePipelineTreeItem(
+ TreeItem executionItem,
+ ExecutionInfoLocation location,
+ Execution execution,
+ ExecutionState state) {
try {
executionItem.setImage(GuiResource.getInstance().getImagePipeline());
String label = execution.getName();
- label +=
- " - "
- + new SimpleDateFormat("yyyy/MM/dd
HH:mm").format(execution.getExecutionStartDate());
+ label += " - " +
START_DATE_FORMAT.format(execution.getExecutionStartDate());
executionItem.setText(label);
executionItem.setData(execution);
+
+ decorateItemWithState(executionItem, location, state);
} catch (Exception e) {
new ErrorDialog(
getShell(), CONST_ERROR1, "Error drawing pipeline execution
information tree item", e);
}
}
- private void decorateWorkflowTreeItem(TreeItem executionItem, Execution
execution) {
+ private void decorateWorkflowTreeItem(
+ TreeItem executionItem,
+ ExecutionInfoLocation location,
+ Execution execution,
+ ExecutionState state) {
try {
executionItem.setImage(GuiResource.getInstance().getImageWorkflow());
@@ -625,19 +977,31 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
+ new SimpleDateFormat("yyyy/MM/dd
HH:mm").format(execution.getExecutionStartDate());
executionItem.setText(label);
executionItem.setData(execution);
+
+ decorateItemWithState(executionItem, location, state);
} catch (Exception e) {
new ErrorDialog(
getShell(), CONST_ERROR1, "Error drawing workflow execution
information tree item", e);
}
}
+ private static void decorateItemWithState(
+ TreeItem executionItem, ExecutionInfoLocation location, ExecutionState
state) {
+ long loggingInterval = Const.toLong(location.getDataLoggingInterval(),
20000);
+
+ if (state.isFailed()) {
+
executionItem.setBackground(GuiResource.getInstance().getColorLightRed());
+ } else if (state.isStale(loggingInterval)) {
+
executionItem.setBackground(GuiResource.getInstance().getColorLightGray());
+ } else if (state.isRunning()) {
+
executionItem.setBackground(GuiResource.getInstance().getColorLightBlueMuted());
+ }
+ }
+
@Override
public boolean remove(IHopFileTypeHandler typeHandler) {
- if (typeHandler instanceof MetadataEditor) {
- MetadataEditor<?> editor = (MetadataEditor<?>) typeHandler;
-
+ if (typeHandler instanceof MetadataEditor<?> editor) {
if (editor.isCloseable()) {
-
viewers.remove(editor);
for (CTabItem item : tabFolder.getItems()) {
@@ -746,15 +1110,6 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
return new ArrayList<>();
}
- /**
- * Gets locationMap
- *
- * @return value of locationMap
- */
- public Map<String, ExecutionInfoLocation> getLocationMap() {
- return locationMap;
- }
-
@Override
public void closeTab(CTabFolderEvent event, CTabItem tabItem) {
IExecutionViewer viewer = (IExecutionViewer) tabItem.getData();
@@ -777,4 +1132,85 @@ public class ExecutionPerspective implements
IHopPerspective, TabClosable {
public CTabFolder getTabFolder() {
return tabFolder;
}
+
+ public void saveState() {
+ try {
+ AuditStateMap stateMap = new AuditStateMap();
+ stateMap.add(
+ new AuditState(
+ AUDIT_EXECUTION_TOOLBAR,
+ Map.of(
+ AUDIT_ONLY_PARENTS,
+ onlyShowingParents,
+ AUDIT_ONLY_FAILED,
+ onlyShowingFailed,
+ AUDIT_ONLY_RUNNING,
+ onlyShowingRunning,
+ AUDIT_ONLY_FINISHED,
+ onlyShowingFinished,
+ AUDIT_ONLY_WORKFLOWS,
+ onlyShowingWorkflows,
+ AUDIT_ONLY_PIPELINES,
+ onlyShowingPipelines,
+ AUDIT_FILTER_TEXT,
+ Const.NVL(filterText, ""),
+ AUDIT_TIME_FILTER,
+ timeFilter.getCode())));
+
+ int[] sashWeights = sash.getWeights();
+ stateMap.add(
+ new AuditState(
+ AUDIT_SASH_WEIGHTS,
+ Map.of(AUDIT_SASH_LEFT, sashWeights[0], AUDIT_SASH_RIGHT,
sashWeights[1])));
+ AuditManager.getActive()
+ .saveAuditStateMap(HopNamespace.getNamespace(),
EXECUTION_AUDIT_TYPE, stateMap);
+ } catch (Exception e) {
+ hopGui.getLog().logError("Error saving execution perspective state", e);
+ }
+ }
+
+ /** Restore the state of this perspective. */
+ public void restoreState() {
+ try {
+ AuditStateMap stateMap =
+ AuditManager.getActive()
+ .loadAuditStateMap(HopNamespace.getNamespace(),
EXECUTION_AUDIT_TYPE);
+ AuditState toolbarState = stateMap.get(AUDIT_EXECUTION_TOOLBAR);
+ if (toolbarState == null) {
+ toolbarState = new AuditState();
+ }
+ onlyShowingParents = toolbarState.extractBoolean(AUDIT_ONLY_PARENTS,
true);
+ onlyShowingFailed = toolbarState.extractBoolean(AUDIT_ONLY_FAILED,
false);
+ onlyShowingFinished = toolbarState.extractBoolean(AUDIT_ONLY_FINISHED,
false);
+ onlyShowingRunning = toolbarState.extractBoolean(AUDIT_ONLY_RUNNING,
false);
+ onlyShowingWorkflows = toolbarState.extractBoolean(AUDIT_ONLY_WORKFLOWS,
false);
+ onlyShowingPipelines = toolbarState.extractBoolean(AUDIT_ONLY_PIPELINES,
false);
+ filterText = toolbarState.extractString(AUDIT_FILTER_TEXT, "");
+ String timeFilterName = toolbarState.extractString(AUDIT_TIME_FILTER,
"");
+ timeFilter = IEnumHasCode.lookupCode(LastPeriod.class, timeFilterName,
LastPeriod.ONE_HOUR);
+
+ AuditState sashState = stateMap.get(AUDIT_SASH_WEIGHTS);
+ if (sashState == null) {
+ sashState = new AuditState();
+ }
+ int left = sashState.extractInteger(AUDIT_SASH_LEFT, 30);
+ int right = sashState.extractInteger(AUDIT_SASH_RIGHT, 70);
+ sash.setWeights(left, right);
+
+ updateGui();
+ refresh();
+ } catch (Exception e) {
+ hopGui.getLog().logError("Error restoring explorer perspective state",
e);
+ }
+ }
+
+ public ExecutionInfoLocation lookupLocation(String locationName) {
+ ExecutionInfoLocation location = locationMap.get(locationName);
+ if (location == null) {
+ // Not yet loaded in the map in the refresh
+ //
+ refresh();
+ }
+ return locationMap.get(locationName);
+ }
}
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 58a9535ace..612e9e84f7 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
@@ -309,17 +309,13 @@ public class PipelineExecutionViewer extends
BaseExecutionViewer
// Calculate information staleness
//
String statusDescription = executionState.getStatusDescription();
- if (Pipeline.STRING_RUNNING.equalsIgnoreCase(statusDescription)
- || Pipeline.STRING_INITIALIZING.equalsIgnoreCase(statusDescription))
{
- long loggingInterval = Const.toLong(location.getDataLoggingInterval(),
20000);
- if (System.currentTimeMillis() -
executionState.getUpdateTime().getTime()
- > loggingInterval) {
- // The information is stale, not getting updates!
- //
- TableItem item = infoView.add("Update state", STRING_STATE_STALE);
- item.setBackground(GuiResource.getInstance().getColorLightBlue());
- item.setForeground(GuiResource.getInstance().getColorWhite());
- }
+ long loggingInterval = Const.toLong(location.getDataLoggingInterval(),
20000);
+ if (executionState.isStale(loggingInterval)) {
+ // The information is stale, not getting updates!
+ //
+ TableItem item = infoView.add("Update state", STRING_STATE_STALE);
+ item.setBackground(GuiResource.getInstance().getColorLightBlue());
+ item.setForeground(GuiResource.getInstance().getColorWhite());
}
infoView.add("Name", execution.getName());
@@ -329,9 +325,15 @@ public class PipelineExecutionViewer extends
BaseExecutionViewer
infoView.add("Parent ID", execution.getParentId());
infoView.add("Registration",
formatDate(execution.getRegistrationDate()));
infoView.add("Start", formatDate(execution.getExecutionStartDate()));
+ infoView.add("End", formatDate(executionState.getExecutionEndDate()));
infoView.add("Type", executionState.getExecutionType().name());
infoView.add("Status", statusDescription);
infoView.add("Status Last updated",
formatDate(executionState.getUpdateTime()));
+ infoView.add(
+ "Failed",
+ executionState.isFailed()
+ ? BaseMessages.getString("System.Button.Yes")
+ : BaseMessages.getString("System.Button.No"));
infoView.add("Container ID", executionState.getContainerId());
infoView.optimizeTableView();
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/WorkflowExecutionViewer.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/WorkflowExecutionViewer.java
index 5b5bb25c19..14716c0042 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/WorkflowExecutionViewer.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/WorkflowExecutionViewer.java
@@ -61,7 +61,6 @@ import org.apache.hop.execution.ExecutionState;
import org.apache.hop.execution.ExecutionType;
import org.apache.hop.execution.IExecutionInfoLocation;
import org.apache.hop.i18n.BaseMessages;
-import org.apache.hop.pipeline.Pipeline;
import org.apache.hop.pipeline.PipelinePainter;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.dialog.ErrorDialog;
@@ -303,17 +302,13 @@ public class WorkflowExecutionViewer extends
BaseExecutionViewer
// Calculate information staleness
//
String statusDescription = executionState.getStatusDescription();
- if (Pipeline.STRING_RUNNING.equalsIgnoreCase(statusDescription)
- || Pipeline.STRING_INITIALIZING.equalsIgnoreCase(statusDescription))
{
- long loggingInterval = Const.toLong(location.getDataLoggingInterval(),
20000);
- if (System.currentTimeMillis() -
executionState.getUpdateTime().getTime()
- > loggingInterval) {
- // The information is stale, not getting updates!
- //
- TableItem item = infoView.add("Update state", STRING_STATE_STALE);
- item.setBackground(GuiResource.getInstance().getColorLightBlue());
- item.setForeground(GuiResource.getInstance().getColorWhite());
- }
+ long loggingInterval = Const.toLong(location.getDataLoggingInterval(),
20000);
+ if (executionState.isStale(loggingInterval)) {
+ // The information is stale, not getting updates!
+ //
+ TableItem item = infoView.add("Update state", STRING_STATE_STALE);
+ item.setBackground(GuiResource.getInstance().getColorLightBlue());
+ item.setForeground(GuiResource.getInstance().getColorWhite());
}
infoView.add("Name", execution.getName());
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/execution/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/execution/messages/messages_en_US.properties
index 6f1b40572d..c27c4de939 100644
---
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/execution/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/execution/messages/messages_en_US.properties
@@ -21,6 +21,7 @@ ExecutionPerspective.Refresh.Error.Header=Error
ExecutionPerspective.Refresh.Error.Message=There was an error refreshing
execution information:
ExecutionPerspective.ToolbarElement.Delete.Tooltip=Delete
ExecutionPerspective.ToolbarElement.Refresh.Tooltip=Refresh information
+ExecutionPerspective.ToolbarElement.ForceRefresh.Tooltip=Force refresh
information (clear caches)
ExecutionViewer.GuiAction.ZoomFitToScreen.Tooltip=Zoom to fit the screen
PipelineExecutionViewer.DataTab.Title=Data
PipelineExecutionViewer.InfoTab.Title=Info
@@ -44,3 +45,27 @@
WorkflowExecutionViewer.ToolbarElement.NavigateToEditor.Tooltip=Edit this workfl
WorkflowExecutionViewer.ToolbarElement.Refresh.Tooltip=Refresh information
WorkflowExecutionViewer.ToolbarElement.ViewExecutor.Tooltip=View this executed
workflow in the data orchestration perspective
WorkflowExecutionViewer.ToolbarElement.ViewMetadata.Tooltip=View the metadata
used when executing this workflow
+ExecutionPerspective.ToolbarElement.SelectParents.Tooltip=Only show the top
level pipelines and workflows.
+ExecutionPerspective.ToolbarElement.SelectFailed.Tooltip=Only show the failed
pipelines and workflows.
+ExecutionPerspective.ToolbarElement.SelectRunning.Tooltip=Only show the
running pipelines and workflows.
+ExecutionPerspective.ToolbarElement.SelectFinished.Tooltip=Only show the
finished pipelines and workflows.
+ExecutionPerspective.ToolbarElement.SelectPipelines.Tooltip=Only show pipelines
+ExecutionPerspective.ToolbarElement.SelectWorkflows.Tooltip=Only show workflows
+ExecutionPerspective.ToolbarElement.FilterText.Tooltip=Filter on name, start
date or ID (and hit enter)
+ExecutionPerspective.ToolbarElement.ClearFilters.Tooltip=Clear all filters
+
+ExecutionPerspective.ToolbarElement.TimeFilter.Tooltip = Select the last
period of executions to show.
+ExecutionPerspective.LastPeriod.None=-
+ExecutionPerspective.LastPeriod.OneHour=1h
+ExecutionPerspective.LastPeriod.TwoHours=2h
+ExecutionPerspective.LastPeriod.SixHours=6h
+ExecutionPerspective.LastPeriod.TwelveHours=12h
+ExecutionPerspective.LastPeriod.OneDay=1d
+ExecutionPerspective.LastPeriod.TwoDays=2d
+ExecutionPerspective.LastPeriod.ThreeDays=3d
+ExecutionPerspective.LastPeriod.FourDays=4d
+ExecutionPerspective.LastPeriod.OneWeek=1w
+ExecutionPerspective.LastPeriod.TwoWeeks=2w
+ExecutionPerspective.LastPeriod.OneMonth=1M
+ExecutionPerspective.LastPeriod.TwoMonths=2M
+ExecutionPerspective.LastPeriod.ThreeMonths=3M
diff --git a/ui/src/main/resources/ui/images/finished-icon-disabled.svg
b/ui/src/main/resources/ui/images/finished-icon-disabled.svg
new file mode 100644
index 0000000000..0e1649f6e8
--- /dev/null
+++ b/ui/src/main/resources/ui/images/finished-icon-disabled.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="16"
+ height="16"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ fill="#D7D7D7"
+ d="m 10.040337,7.008269 -6.3629999,3.692 c -0.54,0.313 -1.233,-0.066
-1.233,-0.697 v -7.384 c 0,-0.63 0.692,-1.01 1.233,-0.696 l 6.3629999,3.692 a
0.802,0.802 0 0 1 0,1.393 z"
+ id="path2"
+ style="fill:#D7D7D7;fill-opacity:1" />
+ <path
+ fill="#D7D7D7"
+ d="m 13.613945,9.1497224 c -0.07168,-0.089541 -0.189224,-0.089541
-0.261004,0 l -2.170214,2.7004696 -1.117686,-1.400496 c -0.071727,-0.08991
-0.1884353,-0.08991 -0.260636,0 l -0.3916365,0.48701 c -0.071727,0.08891
-0.071727,0.235308 0,0.324691 l 1.6385375,2.052665 c 0.07162,0.08881
0.188383,0.08881 0.260846,0 l 2.693272,-3.3522173 c 0.07262,-0.089593
0.07262,-0.2362012 0,-0.3260573 z"
+ id="path2-6"
+ style="stroke-width:0.0525475;fill:#D7D7D7;fill-opacity:1" />
+</svg>
diff --git a/ui/src/main/resources/ui/images/finished-icon.svg
b/ui/src/main/resources/ui/images/finished-icon.svg
new file mode 100644
index 0000000000..b07ef7144e
--- /dev/null
+++ b/ui/src/main/resources/ui/images/finished-icon.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="16"
+ height="16"
+ version="1.1"
+ id="svg4"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ fill="#0e3a5a"
+ d="m 10.040337,7.008269 -6.3629999,3.692 c -0.54,0.313
-1.233,-0.066 -1.233,-0.697 v -7.384 c 0,-0.63 0.692,-1.01 1.233,-0.696 l
6.3629999,3.692 a 0.802,0.802 0 0 1 0,1.393 z"
+ id="path2"
+ style="fill:#0e3a5a;fill-opacity:1"/>
+ <path
+ fill="#0e3a5a"
+ d="m 13.613945,9.1497224 c -0.07168,-0.089541 -0.189224,-0.089541
-0.261004,0 l -2.170214,2.7004696 -1.117686,-1.400496 c -0.071727,-0.08991
-0.1884353,-0.08991 -0.260636,0 l -0.3916365,0.48701 c -0.071727,0.08891
-0.071727,0.235308 0,0.324691 l 1.6385375,2.052665 c 0.07162,0.08881
0.188383,0.08881 0.260846,0 l 2.693272,-3.3522173 c 0.07262,-0.089593
0.07262,-0.2362012 0,-0.3260573 z"
+ id="path2-6"
+ style="stroke-width:0.0525475;fill:#0e3a5a;fill-opacity:1"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/force-refresh.svg
b/ui/src/main/resources/ui/images/force-refresh.svg
new file mode 100644
index 0000000000..b95bc4130e
--- /dev/null
+++ b/ui/src/main/resources/ui/images/force-refresh.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="24"
+ height="24"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ style="fill:#0e3a5a;fill-opacity:1"
+ d="m 10,3 a -9,9 0 0 1 9,9 h 3 L 18.11,15.89 18.04,16.03 14,12 h 3 a -7,7
0 0 0 -7,-7 -7,7 0 0 0 -7,7 -7,7 0 0 0 7,7 c 1.93,0 3.68,-0.79 4.94,-2.06 l
1.42,1.42 C 14.73,20 12.5,21 10,21 A -9,9 0 0 1 1,12 -9,9 0 0 1 10,3 Z"
+ id="path2" />
+ <path
+ d="m 8.44549,5.6567483 h 2.843 v 7.7189997 h -2.843 z m -0.066,9.8679997
h 2.976 v 2.569 h -2.976 z"
+ fill="#ffffff"
+ id="path4"
+ style="fill:#ea102a;fill-opacity:1" />
+</svg>
diff --git a/ui/src/main/resources/ui/images/pipeline-disabled.svg
b/ui/src/main/resources/ui/images/pipeline-disabled.svg
new file mode 100644
index 0000000000..9116dc336b
--- /dev/null
+++ b/ui/src/main/resources/ui/images/pipeline-disabled.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
+ <path d="M16.429 4a7.52 7.52 0 0 0-.682.028c-1.782.155-3.29.865-4.273
1.328-1.033.486-2.334 1.25-3.703 1.585s-2.682.284-3.976-.82a1.118 1.29 0 1
0-1.33 2.074c1.886 1.609 4.02 1.697 5.769 1.27s3.242-1.323 4.085-1.72c.969-.456
2.237-1.026 3.597-1.144s2.775.161 4.259 1.507a1.118 1.29 0 1 0
1.382-2.027C19.865 4.545 18.051 4.007 16.429 4zm0 5.159a7.52 7.52 0 0
0-.682.028c-1.782.155-3.29.865-4.273 1.328-1.033.486-2.334 1.25-3.703
1.585s-2.682.284-3.976-.82a1.118 1.29 0 1 0-1.33 2.074c1.886 [...]
+ fill="#D7D7D7"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/run-light.svg
b/ui/src/main/resources/ui/images/run-light.svg
new file mode 100644
index 0000000000..0ac553c5cd
--- /dev/null
+++ b/ui/src/main/resources/ui/images/run-light.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="16"
+ height="16"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ fill="#0e3a5a"
+ d="M11.596 8.697l-6.363
3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363
3.692a.802.802 0 0 1 0 1.393z"
+ id="path2"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/running-icon-disabled.svg
b/ui/src/main/resources/ui/images/running-icon-disabled.svg
new file mode 100644
index 0000000000..7f259096eb
--- /dev/null
+++ b/ui/src/main/resources/ui/images/running-icon-disabled.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="16"
+ height="16"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ fill="#D7D7D7"
+ d="m 10.040337,7.008269 -6.3629999,3.692 c -0.54,0.313
-1.233,-0.066 -1.233,-0.697 v -7.384 c 0,-0.63 0.692,-1.01 1.233,-0.696 l
6.3629999,3.692 a 0.802,0.802 0 0 1 0,1.393 z"
+ id="path2"
+ style="fill:#D7D7D7"/>
+ <path
+ style="fill:#D7D7D7;fill-opacity:1;stroke-width:0.314263"
+ d="m 11.488513,9.0704402 a 2.8283707,2.8283707 0 0 1
2.828371,2.8283708 h 0.94279 l -1.222484,1.222485 -0.022,0.04399
-1.269624,-1.266482 h 0.94279 a 2.1998438,2.1998438 0 0 0 -2.199844,-2.1998436
2.1998438,2.1998438 0 0 0 -2.199844,2.1998436 2.1998438,2.1998438 0 0 0
2.199844,2.199845 c 0.606529,0 1.156489,-0.248268 1.552461,-0.647384 l
0.446255,0.446255 c -0.512249,0.515392 -1.213058,0.829655 -1.998716,0.829655 A
2.8283707,2.8283707 0 0 1 8.6601421,11.898811 2.8283707,2.828 [...]
+ id="path2-3"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/running-icon.svg
b/ui/src/main/resources/ui/images/running-icon.svg
new file mode 100644
index 0000000000..3d091b6b3a
--- /dev/null
+++ b/ui/src/main/resources/ui/images/running-icon.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="16"
+ height="16"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ fill="#0e3a5a"
+ d="m 10.040337,7.008269 -6.3629999,3.692 c -0.54,0.313
-1.233,-0.066 -1.233,-0.697 v -7.384 c 0,-0.63 0.692,-1.01 1.233,-0.696 l
6.3629999,3.692 a 0.802,0.802 0 0 1 0,1.393 z"
+ id="path2"
+ style="fill:#0000ff"/>
+ <path
+ style="fill:#0000ff;fill-opacity:1;stroke-width:0.314263"
+ d="m 11.488513,9.0704402 a 2.8283707,2.8283707 0 0 1
2.828371,2.8283708 h 0.94279 l -1.222484,1.222485 -0.022,0.04399
-1.269624,-1.266482 h 0.94279 a 2.1998438,2.1998438 0 0 0 -2.199844,-2.1998436
2.1998438,2.1998438 0 0 0 -2.199844,2.1998436 2.1998438,2.1998438 0 0 0
2.199844,2.199845 c 0.606529,0 1.156489,-0.248268 1.552461,-0.647384 l
0.446255,0.446255 c -0.512249,0.515392 -1.213058,0.829655 -1.998716,0.829655 A
2.8283707,2.8283707 0 0 1 8.6601421,11.898811 2.8283707,2.828 [...]
+ id="path2-3"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/up-disabled.svg
b/ui/src/main/resources/ui/images/up-disabled.svg
new file mode 100644
index 0000000000..c85f8b3359
--- /dev/null
+++ b/ui/src/main/resources/ui/images/up-disabled.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
+ <path fill="#D7D7D7"
d="M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z"/>
+</svg>
diff --git a/ui/src/main/resources/ui/images/workflow-disabled.svg
b/ui/src/main/resources/ui/images/workflow-disabled.svg
new file mode 100644
index 0000000000..a4bcd639c4
--- /dev/null
+++ b/ui/src/main/resources/ui/images/workflow-disabled.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
+ <path fill="#D7D7D7" d="M12.033 2c-.401 0-.802.153-1.109.461L7.726 5.659
8.86 6.792l2.73-2.73c.123-.123.283-.184.443-.184s.321.061.443.184l2.134
2.134-1.154 1.154H16.9V3.906l-1.157 1.157-2.602-2.602C12.835 2.154 12.434 2
12.033 2zM3.905 7.1l1.157 1.157-2.602 2.602c-.307.307-.461.708-.461
1.109s.153.802.461 1.109l3.198 3.198
1.134-1.134-2.73-2.73c-.123-.123-.184-.283-.184-.443s.061-.321.184-.443L6.196
9.39l1.154 1.154V7.1zm14.436.627l-1.134 1.134 2.73
2.73c.123.123.184.283.184.443s-.0 [...]
+</svg>
\ No newline at end of file