You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@hop.apache.org by "bamaer (via GitHub)" <gi...@apache.org> on 2026/09/30 11:40:15 UTC
[PR] Fixes #5687 : Generate the configuration perspective option tables fr… (hop)
bamaer opened a new pull request, #8706:
URL: https://github.com/apache/hop/pull/8706
…om the plugins
The Plugins section of the configuration perspective is now documented from the @ConfigPlugin and @GuiWidgetElement annotations it is built from: 14 plugins, 84 options. A new config-doc module reads the Jandex indexes the build already writes and instantiates each plugin's configClass, so 70 of the 84 defaults are the values the code really uses rather than a copy. The other 14 carry a defaultValue on the annotation, guarded by tests that check each one against what actually loads.
It runs as part of a normal build, so changing an option updates the page describing it, and a CI check fails a pull request whose generated pages no longer match the code. Releases skip it: what is committed has already been checked on merge.
Also fills in 13 options that had no tooltip in the GUI, and rewrites the General and Look & Feel sections, which had four options that no longer exist, five filed under the wrong tab, and around 30 undocumented.
Fixes the welcome dialog option, which read and wrote 'do not show' straight into a checkbox labelled 'show', so ticking it hid the dialog.
**Please** add a meaningful description for your change here
------------------------
Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
- [x] Run `mvn clean install apache-rat:check` to make sure basic checks pass. A more thorough check will be performed on your pull request automatically.
- [x] If you have a group of commits related to the same change, please squash your commits into one and force push your branch using `git rebase -i`.
- [x] Mention the appropriate issue in your description (for example: `addresses #123`), if applicable.
To make clear that you license your contribution under the [Apache License Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0)
you have to acknowledge this by using the following check-box.
- [x] I hereby declare this contribution to be licensed under the [Apache License Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0)
- [ ] In any other case, please file an [Apache Individual Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@hop.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fixes #5687 : Generate the configuration perspective option tables fr… (hop)
Posted by "bamaer (via GitHub)" <gi...@apache.org>.
bamaer commented on PR #8706:
URL: https://github.com/apache/hop/pull/8706#issuecomment-5938203867
Thanks Matt, all five addressed:
- **Follow system dark mode:** default documented as yes.
- **Dark mode:** default now says off on Windows, follows the OS on macOS and other desktops.
- **Enum defaults:** the generated cell now shows `External browser`. I didn't call `getLabel()` directly, though. The generator's classpath only has the plugin jars, and `BaseMessages` would pull in `HopConfig` and the builder's locale, so the output would vary between machines and trip the drift check. It reads `<Enum>.<NAME>` from the enum's en_US bundle instead, which is the same key `getLabel()` uses, and falls back to `name()`.
- **configClass failures:** the build now fails with an `IllegalStateException` naming the class and plugin.
- **Welcome dialog write:** added `theWelcomeDialogOptionIsStoredInverted`, which checks both polarities against an in-memory `HopConfig`. It fails with the `!` removed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@hop.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fixes #5687 : Generate the configuration perspective option tables fr… (hop)
Posted by "mattcasters (via GitHub)" <gi...@apache.org>.
mattcasters commented on code in PR #8706:
URL: https://github.com/apache/hop/pull/8706#discussion_r4153897824
##########
docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc:
##########
@@ -28,125 +31,275 @@ The configuration perspective offers a central location for all Apache Hop confi
== Usage
-The configuration perspective groups configuration options in the following tabs:
+The perspective lists its sections in a tree on the left. Selecting one shows its options on the right; the search box above the tree finds an option by name across all of them.
-* **General** options control the behavior of Apache Hop and Hop Gui.
-* **Look & Feel** control how Hop Gui looks like on your desktop or in your browser.
-* **Plugin** options provide configuration options to control the behavior of your available Apache Hop plugins (including Explorer, xref:hop-gui/perspective-search.adoc[Search] limits, and xref:hop-gui/perspective-ai-advisor.adoc[AI Assistant] enablement).
-* **System variables** allows you to set and configure Apache Hop global variables.
+* **General**: how Apache Hop and Hop GUI behave - confirmations, tooltips and dialog positions.
+* **Look & Feel**: how Hop GUI is drawn on your desktop or in your browser.
+* **Plugins**: one entry per plugin that contributes options, including xref:hop-gui/perspective-search.adoc[Search], the Explorer perspective and xref:hop-gui/perspective-ai-advisor.adoc[AI Assistant]. Every option is listed in <<_plugin_options_reference,Plugin options reference>> below.
+* **System variables**: set and inspect the Apache Hop global variables.
+* **Security**: how a session is authenticated, split over General, Basic, External and OAuth sub-tabs. Until authentication is turned on the session is unrestricted and anyone can change these settings, which the tab says at the top.
+* **Keyboard shortcuts**: the shortcuts currently in effect, with the action each one triggers.
-== Detailed configuration tabs overview
+== Detailed options overview
=== General
-The General configuration tab allows you to configure the core Apache Hop options.
+The General tab holds the options that control how Hop GUI behaves rather than how it looks. The tables below follow the sections of the tab.
[%header, width=90%, cols="2,5,1"]
|===
|Option|Description|Default
+
|Hop configuration filename
-a|the location where Apache Hop will store the configuration file. This configuration file will contain your project and environment information and global variables.
+a|Where Apache Hop stores `hop-config.json`, the file holding your projects, environments and global variables. Read-only here.
-TIP: set the environment variable `HOP_CONFIG_FOLDER` to set this location dynamically to allow easy switching between different Apache Hop versions and installations. xref:protips/index.adoc[read more]
+TIP: set the environment variable `HOP_CONFIG_FOLDER` to change this location, which makes switching between Hop versions and installations easy. xref:protips/index.adoc[read more]
|`<INSTALLATION_PATH>/config/hop-config.json`
|Preview data batch size
-a|number of rows that will be shown when previewing through the Pipeline run toolbar or from transform preview dialogs.
+a|How many rows are fetched when previewing from the pipeline run toolbar or a transform preview dialog.
-INFO: this setting complements the preview option that is used in the xref:pipeline/pipeline-run-configurations/native-local-pipeline-engine.adoc[native local pipeline run configuration], which can be accessed through the image:transforms/icons/datagrid.svg[data grid, width="16px"] `preview data` icon in the bottom right corner of transforms after a successful run.
+INFO: this complements the preview setting of the xref:pipeline/pipeline-run-configurations/native-local-pipeline-engine.adoc[native local pipeline run configuration], reachable through the image:transforms/icons/datagrid.svg[data grid, width="16px"] `preview data` icon in the bottom right corner of a transform after a run.
|1000
-|Use database cache|enable caching for relational databases|yes
+|Use database cache|Cache relational database metadata so repeated lookups of tables, fields and types do not go back to the server.|yes
-|Open last file at startup|reopen the last used files and select the last used file when Hop GUI starts|yes
+|Reopen tabs on startup|Reopen the files that were open when Hop GUI last closed, and select the one that was active.|yes
|Enable embedded terminal
-a|Show the embedded terminal in the bottom panel. Clear this on desktops where that terminal misbehaves. Search, the database workbench, and the VFS file explorer stay available. Hop Web has no embedded terminal. Excluding `40010-menu-tools-terminal` in xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml] forces it off.
+a|Show the embedded terminal in the bottom panel. Clear this on desktops where that terminal misbehaves; search, the database workbench and the VFS file explorer stay available either way. Hop Web has no embedded terminal. Excluding `40010-menu-tools-terminal` in xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml] forces it off.
|yes
-|Autosave changed files|Automatically save changed workflow and pipelines files|yes
+|Reload file if changed on filesystem|Reload a pipeline or workflow when it changes outside Hop GUI, for example after a `git pull` or an edit in another editor.|no
-|Automatically split hops
-a|Automatically split hops when a new transform or action is dropped on a hop.
+|Sort field names alphabetically in dropdowns|Sort field names in dropdown lists alphabetically. When off, fields keep the order they arrive in from the incoming stream.|yes
-TIP: Hops will be shown in bold when a transform or action can be dropped on it
-|yes
+|Use global bookmarks in the file dialog|Share the file dialog's bookmarks across every project instead of keeping a separate set per project.|yes
+
+|Maximum execution logging text size|The largest amount of log text held for the execution information perspective, in characters. Caps memory use on long-running or very chatty executions.|2000000
+|===
-|Show Copy or Distribute Dialog|Show a pop-up dialog to ask the user to confirm whether data should be copied or distributed (round-robin) to subsequent transforms in a pipeline|yes
+==== Confirmation dialogs
-|Ask user when exiting|Show a pop-up dialog to let the user confirm before exiting Hop GUI|yes
+Each option here controls one confirmation pop-up. Turning one off does not change what the action does, only whether Hop asks first. Every one of these dialogs can also be switched off from the dialog itself, with its *Do not ask this again* option.
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
-|Clear customer parameters (transforms/plugins|Deletes all custom flags and parameters in the dialogs of the transforms and plugins|on click
+|Show confirmation when exiting Hop|Ask for confirmation before closing Hop GUI.|yes
-|Auto collapse palette tree|Collapse or expand the categories in the context dialog|no (expanded)
+|Show copy or distribute dialog when drawing a hop|Ask whether rows should be copied to, or distributed round-robin over, the following transforms when a second hop is drawn out of a transform.|yes
-|Display tooltips|Show tooltips where available in Hop GUI|yes
+|Show confirmation when splitting hops|Ask before splitting a hop when a transform or action is dropped onto it. With this off, the hop is split without asking. Hops that a dropped item would split are drawn in bold.|yes
-|Show help tooltips|Show help tooltips where available in Hop GUI|yes
+|Show confirmation to save file when starting pipeline or workflow|Ask whether to save a changed file before running it. With this off, the file is saved automatically on every run.|yes
+
+|Reset all to enabled (Includes Action/Transform warnings)|Button. Switches every confirmation dialog back on, including the per-transform and per-action warnings that were dismissed with *Do not ask this again*.|on click
+|===
+
+==== Tooltips
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
-|Use double click on canvas?
-|double-click instead of single click on e.g. transform and action dialogs.
+|Show tooltip when hovering over a hop or Action/Transform|The master switch for the canvas tooltips. With this off, none of them appear, whatever is ticked under *Tooltips* in the xref:hop-gui/perspective-configuration.adoc#_look_feel[Look & Feel] tab.|yes
+
+|Show resolved variables in tooltips|Show what a variable currently resolves to in the tooltip, rather than the `+'${VARIABLE}'+` expression as written.|yes
+
+|Reset all to enabled|Button. Switches every tooltip option back on.|on click
+|===
+
+==== Dialog positioning
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
+
+|Remember dialog positions|Reopen each dialog at the position and size it was last given. With this off, every dialog opens at its default position and size.|yes
+
+|Reset dialog positions after restart|Keep remembered positions for the current session only, clearing `shells-state.json` on restart. With this off, positions are also written to that file and restored the next time Hop GUI starts. Only has an effect while *Remember dialog positions* is on.|no
+
+|Clear dialog positions|Button. Forgets every remembered dialog position and resets the perspective tree-panel widths to their defaults. Useful after moving between monitors of different sizes, when a dialog reopens off-screen.|on click
+|===
+
+=== Look & Feel
-enabled: double-click on a transform or action icon to open its properties. A single click on the icon opens the context dialog.
-disabled (default): (single) click on a transform or action icon to open the context dialog. (single) click on a transform or action name to open its properties.
+The Look & Feel tab holds everything about how Hop GUI is drawn: fonts, colours, the canvas, and which columns and toolbars are shown. The tables below follow the sections of the tab.
+
+Some of these settings only take effect after Hop GUI is restarted; the dialog says so where that is the case.
+
+==== General appearance
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
+
+|Preferred language
+a|The language Hop GUI is shown in.
+
+TIP: Check the https://hop.apache.org/community/contribution-guides/translation-contribution-guide/[Translation Contribution Guide] to translate Apache Hop into your own language.
+|English (US)
+
+|Hide the menu bar|Hide the menu bar. With it hidden, the menu is reachable from the Apache Hop icon in the upper left corner of Hop GUI.|yes
+
+|Dark mode|Use the dark theme. On Hop Web and Windows this is a straight toggle; on macOS and other desktop platforms the theme follows the operating system. Changing it in Hop Web reloads the page.|follows the OS
+
+|Follow system dark mode (Hop Web)|Take the light or dark setting from the system or browser. While this is on, the *Dark mode* option above is disabled.|no
Review Comment:
**[bug]** The default is on, not off. `ConfigGuiOptionsTab.reloadValues()` starts `followSystem` at `true` and only replaces it from the Hop Web audit state (`extractBoolean("followSystem", true)`). Opening the configuration perspective runs that reload, so a session that has never stored the option shows the box checked and disables Dark mode. The `false` passed to `createCheckbox` is only the value before that reload.
**Suggestion:** Document the default as yes.
##########
docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc:
##########
@@ -28,125 +31,275 @@ The configuration perspective offers a central location for all Apache Hop confi
== Usage
-The configuration perspective groups configuration options in the following tabs:
+The perspective lists its sections in a tree on the left. Selecting one shows its options on the right; the search box above the tree finds an option by name across all of them.
-* **General** options control the behavior of Apache Hop and Hop Gui.
-* **Look & Feel** control how Hop Gui looks like on your desktop or in your browser.
-* **Plugin** options provide configuration options to control the behavior of your available Apache Hop plugins (including Explorer, xref:hop-gui/perspective-search.adoc[Search] limits, and xref:hop-gui/perspective-ai-advisor.adoc[AI Assistant] enablement).
-* **System variables** allows you to set and configure Apache Hop global variables.
+* **General**: how Apache Hop and Hop GUI behave - confirmations, tooltips and dialog positions.
+* **Look & Feel**: how Hop GUI is drawn on your desktop or in your browser.
+* **Plugins**: one entry per plugin that contributes options, including xref:hop-gui/perspective-search.adoc[Search], the Explorer perspective and xref:hop-gui/perspective-ai-advisor.adoc[AI Assistant]. Every option is listed in <<_plugin_options_reference,Plugin options reference>> below.
+* **System variables**: set and inspect the Apache Hop global variables.
+* **Security**: how a session is authenticated, split over General, Basic, External and OAuth sub-tabs. Until authentication is turned on the session is unrestricted and anyone can change these settings, which the tab says at the top.
+* **Keyboard shortcuts**: the shortcuts currently in effect, with the action each one triggers.
-== Detailed configuration tabs overview
+== Detailed options overview
=== General
-The General configuration tab allows you to configure the core Apache Hop options.
+The General tab holds the options that control how Hop GUI behaves rather than how it looks. The tables below follow the sections of the tab.
[%header, width=90%, cols="2,5,1"]
|===
|Option|Description|Default
+
|Hop configuration filename
-a|the location where Apache Hop will store the configuration file. This configuration file will contain your project and environment information and global variables.
+a|Where Apache Hop stores `hop-config.json`, the file holding your projects, environments and global variables. Read-only here.
-TIP: set the environment variable `HOP_CONFIG_FOLDER` to set this location dynamically to allow easy switching between different Apache Hop versions and installations. xref:protips/index.adoc[read more]
+TIP: set the environment variable `HOP_CONFIG_FOLDER` to change this location, which makes switching between Hop versions and installations easy. xref:protips/index.adoc[read more]
|`<INSTALLATION_PATH>/config/hop-config.json`
|Preview data batch size
-a|number of rows that will be shown when previewing through the Pipeline run toolbar or from transform preview dialogs.
+a|How many rows are fetched when previewing from the pipeline run toolbar or a transform preview dialog.
-INFO: this setting complements the preview option that is used in the xref:pipeline/pipeline-run-configurations/native-local-pipeline-engine.adoc[native local pipeline run configuration], which can be accessed through the image:transforms/icons/datagrid.svg[data grid, width="16px"] `preview data` icon in the bottom right corner of transforms after a successful run.
+INFO: this complements the preview setting of the xref:pipeline/pipeline-run-configurations/native-local-pipeline-engine.adoc[native local pipeline run configuration], reachable through the image:transforms/icons/datagrid.svg[data grid, width="16px"] `preview data` icon in the bottom right corner of a transform after a run.
|1000
-|Use database cache|enable caching for relational databases|yes
+|Use database cache|Cache relational database metadata so repeated lookups of tables, fields and types do not go back to the server.|yes
-|Open last file at startup|reopen the last used files and select the last used file when Hop GUI starts|yes
+|Reopen tabs on startup|Reopen the files that were open when Hop GUI last closed, and select the one that was active.|yes
|Enable embedded terminal
-a|Show the embedded terminal in the bottom panel. Clear this on desktops where that terminal misbehaves. Search, the database workbench, and the VFS file explorer stay available. Hop Web has no embedded terminal. Excluding `40010-menu-tools-terminal` in xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml] forces it off.
+a|Show the embedded terminal in the bottom panel. Clear this on desktops where that terminal misbehaves; search, the database workbench and the VFS file explorer stay available either way. Hop Web has no embedded terminal. Excluding `40010-menu-tools-terminal` in xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml] forces it off.
|yes
-|Autosave changed files|Automatically save changed workflow and pipelines files|yes
+|Reload file if changed on filesystem|Reload a pipeline or workflow when it changes outside Hop GUI, for example after a `git pull` or an edit in another editor.|no
-|Automatically split hops
-a|Automatically split hops when a new transform or action is dropped on a hop.
+|Sort field names alphabetically in dropdowns|Sort field names in dropdown lists alphabetically. When off, fields keep the order they arrive in from the incoming stream.|yes
-TIP: Hops will be shown in bold when a transform or action can be dropped on it
-|yes
+|Use global bookmarks in the file dialog|Share the file dialog's bookmarks across every project instead of keeping a separate set per project.|yes
+
+|Maximum execution logging text size|The largest amount of log text held for the execution information perspective, in characters. Caps memory use on long-running or very chatty executions.|2000000
+|===
-|Show Copy or Distribute Dialog|Show a pop-up dialog to ask the user to confirm whether data should be copied or distributed (round-robin) to subsequent transforms in a pipeline|yes
+==== Confirmation dialogs
-|Ask user when exiting|Show a pop-up dialog to let the user confirm before exiting Hop GUI|yes
+Each option here controls one confirmation pop-up. Turning one off does not change what the action does, only whether Hop asks first. Every one of these dialogs can also be switched off from the dialog itself, with its *Do not ask this again* option.
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
-|Clear customer parameters (transforms/plugins|Deletes all custom flags and parameters in the dialogs of the transforms and plugins|on click
+|Show confirmation when exiting Hop|Ask for confirmation before closing Hop GUI.|yes
-|Auto collapse palette tree|Collapse or expand the categories in the context dialog|no (expanded)
+|Show copy or distribute dialog when drawing a hop|Ask whether rows should be copied to, or distributed round-robin over, the following transforms when a second hop is drawn out of a transform.|yes
-|Display tooltips|Show tooltips where available in Hop GUI|yes
+|Show confirmation when splitting hops|Ask before splitting a hop when a transform or action is dropped onto it. With this off, the hop is split without asking. Hops that a dropped item would split are drawn in bold.|yes
-|Show help tooltips|Show help tooltips where available in Hop GUI|yes
+|Show confirmation to save file when starting pipeline or workflow|Ask whether to save a changed file before running it. With this off, the file is saved automatically on every run.|yes
+
+|Reset all to enabled (Includes Action/Transform warnings)|Button. Switches every confirmation dialog back on, including the per-transform and per-action warnings that were dismissed with *Do not ask this again*.|on click
+|===
+
+==== Tooltips
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
-|Use double click on canvas?
-|double-click instead of single click on e.g. transform and action dialogs.
+|Show tooltip when hovering over a hop or Action/Transform|The master switch for the canvas tooltips. With this off, none of them appear, whatever is ticked under *Tooltips* in the xref:hop-gui/perspective-configuration.adoc#_look_feel[Look & Feel] tab.|yes
+
+|Show resolved variables in tooltips|Show what a variable currently resolves to in the tooltip, rather than the `+'${VARIABLE}'+` expression as written.|yes
+
+|Reset all to enabled|Button. Switches every tooltip option back on.|on click
+|===
+
+==== Dialog positioning
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
+
+|Remember dialog positions|Reopen each dialog at the position and size it was last given. With this off, every dialog opens at its default position and size.|yes
+
+|Reset dialog positions after restart|Keep remembered positions for the current session only, clearing `shells-state.json` on restart. With this off, positions are also written to that file and restored the next time Hop GUI starts. Only has an effect while *Remember dialog positions* is on.|no
+
+|Clear dialog positions|Button. Forgets every remembered dialog position and resets the perspective tree-panel widths to their defaults. Useful after moving between monitors of different sizes, when a dialog reopens off-screen.|on click
+|===
+
+=== Look & Feel
-enabled: double-click on a transform or action icon to open its properties. A single click on the icon opens the context dialog.
-disabled (default): (single) click on a transform or action icon to open the context dialog. (single) click on a transform or action name to open its properties.
+The Look & Feel tab holds everything about how Hop GUI is drawn: fonts, colours, the canvas, and which columns and toolbars are shown. The tables below follow the sections of the tab.
+
+Some of these settings only take effect after Hop GUI is restarted; the dialog says so where that is the case.
+
+==== General appearance
+
+[%header, width=90%, cols="2,5,1"]
+|===
+|Option|Description|Default
+
+|Preferred language
+a|The language Hop GUI is shown in.
+
+TIP: Check the https://hop.apache.org/community/contribution-guides/translation-contribution-guide/[Translation Contribution Guide] to translate Apache Hop into your own language.
+|English (US)
+
+|Hide the menu bar|Hide the menu bar. With it hidden, the menu is reachable from the Apache Hop icon in the upper left corner of Hop GUI.|yes
+
+|Dark mode|Use the dark theme. On Hop Web and Windows this is a straight toggle; on macOS and other desktop platforms the theme follows the operating system. Changing it in Hop Web reloads the page.|follows the OS
Review Comment:
**[suggestion]** The Default cell says the theme follows the OS, but that is only the non-Windows desktop path. On Windows and Hop Web `ConfigGuiOptionsTab` uses `props.isDarkMode()`, and `PropsUi.isDarkMode()` falls back to `N`, so Windows comes up in the light theme. The sentence already says Windows is a toggle; the default column should say the same (light / off on Windows, follows the OS on macOS and other desktops).
##########
ui/src/main/java/org/apache/hop/ui/hopgui/welcome/WelcomeDialogOptions.java:
##########
@@ -167,6 +172,6 @@ public void widgetModified(
@Override
public void persistContents(GuiCompositeWidgets compositeWidgets) {
HopConfig.getInstance()
- .saveOption(WelcomeDialog.HOP_CONFIG_NO_SHOW_OPTION, welcomeDialogShowAtStartup);
+ .saveOption(WelcomeDialog.HOP_CONFIG_NO_SHOW_OPTION, !welcomeDialogShowAtStartup);
Review Comment:
**[suggestion]** The constructor inversion is covered by `ConfigPluginDefaultsTest`, but this write is not. `persistContents` does not use the widget argument, so a unit test can set `welcomeDialogShowAtStartup` and call `persistContents(null)`, then assert `doNotShowWelcomeDialog` is the opposite. Reverting only this line would put the checkbox bug back without failing the new test.
**Suggestion:** Assert both polarities: show stores `false`, hide stores `true`.
##########
config-doc/src/main/java/org/apache/hop/config/doc/ConfigDocGenerator.java:
##########
@@ -0,0 +1,526 @@
+/*
+ * 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.config.doc;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.FieldInfo;
+import org.jboss.jandex.Index;
+import org.jboss.jandex.IndexReader;
+
+/**
+ * Generates one AsciiDoc partial per configuration plugin from the annotations the configuration
+ * perspective already builds its widgets from. Build time only: it reads the Jandex indexes the
+ * build writes into every module and the message bundles next to the classes, and instantiates only
+ * the plain configuration objects, never Hop itself.
+ */
+public class ConfigDocGenerator {
+
+ static final DotName CONFIG_PLUGIN =
+ DotName.createSimple("org.apache.hop.core.config.plugin.ConfigPlugin");
+ static final DotName GUI_WIDGET =
+ DotName.createSimple("org.apache.hop.core.gui.plugin.GuiWidgetElement");
+ static final DotName GUI_PLUGIN =
+ DotName.createSimple("org.apache.hop.core.gui.plugin.GuiPlugin");
+ static final String PARENT_ID = "EnterOptionsDialog-GuiWidgetsParent";
+ static final String JANDEX_INDEX = "META-INF/jandex.idx";
+ static final String AGGREGATE = "all-plugins.adoc";
+
+ /** First line of every page this generator owns, and how it tells them from hand-written ones. */
+ static final String GENERATED_MARKER =
+ "// Generated from the @ConfigPlugin and @GuiWidgetElement annotations.";
+
+ static final String LICENSE =
+ """
+ ////
+ 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.
+ ////
+ """;
+
+ record Option(
+ String order, String label, String toolTip, String type, boolean variables, String dflt) {}
+
+ record Plugin(
+ String id, String description, String name, String configKey, List<Option> options) {}
+
+ public static void main(String[] args) throws Exception {
+ Path root = Paths.get(args[0]).toAbsolutePath().normalize();
+ Path outDir = Paths.get(args[1]).toAbsolutePath().normalize();
+
+ // Scan this program's own classpath rather than the source tree. Every module declaring a
+ // @ConfigPlugin is a declared dependency, so each one is here whether it was just built in the
+ // reactor or resolved as a jar from the local repository. Walking target/ directories instead
+ // would find nothing for the modules an incremental build left alone.
+ List<Source> sources = new ArrayList<>();
+ for (String entry : System.getProperty("java.class.path").split(File.pathSeparator)) {
+ Source source = Source.of(Paths.get(entry));
+ if (source != null) sources.add(source);
+ }
+
+ List<Plugin> plugins = new ArrayList<>();
+ for (Source source : sources) {
+ Index index;
+ try (InputStream in = source.open(JANDEX_INDEX)) {
+ if (in == null) continue;
+ index = new IndexReader(in).read();
+ } catch (Exception e) {
+ continue;
+ }
+ for (AnnotationInstance ai : index.getAnnotations(CONFIG_PLUGIN)) {
+ if (ai.target().kind() != AnnotationTarget.Kind.CLASS) continue;
+ Plugin p = readPlugin(ai.target().asClass(), ai, source);
+ if (p != null) plugins.add(p);
+ }
+ }
+ plugins.sort(Comparator.comparing(Plugin::name, String.CASE_INSENSITIVE_ORDER));
+
+ // Only the pages this generator wrote are its business. Anything else in the directory was
+ // put there by hand and is left alone, neither counted nor deleted.
+ List<Path> generatedBefore = generatedPages(outDir);
+
+ // Order matters here. An empty classpath means every plugin looks like one this generator
+ // cannot see, and the report below would then name all of them and ask for fourteen new
+ // dependencies - true in its way, and useless. "There is nothing here at all" is the more
+ // fundamental thing to have gone wrong, so it is the one to say.
+ if (plugins.isEmpty() && !generatedBefore.isEmpty()) {
+ throw new IllegalStateException(
+ "No configuration plugins found on the classpath, but "
+ + outDir.getFileName()
+ + " documents "
+ + generatedBefore.size()
+ + ". Refusing to replace those pages with nothing. This means the classpath is"
+ + " missing the modules that declare a @ConfigPlugin - build the project first.");
+ }
+
+ reportPluginsOutsideTheClasspath(root, plugins);
+
+ Files.createDirectories(outDir);
+ Set<Path> written = new HashSet<>();
+ for (Plugin p : plugins) {
+ Path file = outDir.resolve(p.id() + ".adoc");
+ Files.writeString(file, render(p), StandardCharsets.UTF_8);
+ written.add(file);
+ System.out.printf(
+ "%-34s %2d options -> %s%n", p.name(), p.options().size(), root.relativize(file));
+ }
+
+ // One page wants the lot: a section per plugin, in the order the perspective's tree shows them.
+ StringBuilder all = new StringBuilder(LICENSE);
+ all.append(GENERATED_MARKER).append("\n// Edit those, not this file.\n");
+ for (Plugin p : plugins) {
+ all.append("\n== ").append(p.name()).append("\n\n").append(body(p));
+ }
+ Path index = outDir.resolve(AGGREGATE);
+ Files.writeString(index, all.toString(), StandardCharsets.UTF_8);
+ written.add(index);
+ System.out.println("aggregate -> " + root.relativize(index));
+
+ // A plugin that was renamed or removed leaves its page behind, and the pages that include one
+ // by name would go on rendering it for good. Nothing reads a generated page that this run did
+ // not write, so it goes.
+ for (Path stale : generatedBefore) {
+ if (!written.contains(stale)) {
+ Files.delete(stale);
+ System.out.println("removed (no longer generated) -> " + root.relativize(stale));
+ }
+ }
+ System.out.println(
+ "\n"
+ + plugins.size()
+ + " configuration plugins, "
+ + plugins.stream().mapToInt(p -> p.options().size()).sum()
+ + " options");
+ }
+
+ /**
+ * Which modules a documented plugin can come from is decided by this module's dependencies, and
+ * that list is written by hand. A plugin added to a module that is not on it would be absent from
+ * the manual with nothing to say so: the page set would not change, so the drift check in CI
+ * would pass, and the option would simply never be written about.
+ *
+ * <p>So: sweep what the build has actually compiled and see whether anything turns up that the
+ * classpath did not. This is deliberately only a report - it never adds to the pages, because the
+ * set of modules on disk depends on what this particular build chose to compile, and the pages
+ * have to come out the same everywhere. An incremental build that has not compiled the new module
+ * simply finds nothing, which is why this is best-effort. A full build - every push to main is
+ * one - does find it.
+ */
+ static void reportPluginsOutsideTheClasspath(Path root, List<Plugin> documented)
+ throws IOException {
+ Set<String> known = new HashSet<>();
+ for (Plugin p : documented) {
+ known.add(p.id());
+ }
+
+ Map<String, String> missing = new TreeMap<>();
+ try (Stream<Path> walk = Files.walk(root)) {
+ for (Path index : walk.filter(ConfigDocGenerator::isModuleIndex).toList()) {
+ Path classes = index.getParent().getParent();
+ Source source = Source.of(classes);
+ if (source == null) continue;
+ Index jandex;
+ try (InputStream in = source.open(JANDEX_INDEX)) {
+ if (in == null) continue;
+ jandex = new IndexReader(in).read();
+ } catch (Exception e) {
+ continue;
+ }
+ for (AnnotationInstance ai : jandex.getAnnotations(CONFIG_PLUGIN)) {
+ if (ai.target().kind() != AnnotationTarget.Kind.CLASS) continue;
+ String id = str(ai, "id", "");
+ if (known.contains(id) || !hasPerspectiveWidgets(ai.target().asClass())) continue;
+ // target/classes/META-INF/jandex.idx -> the module directory
+ missing.put(id, root.relativize(classes.getParent().getParent()).toString());
+ }
+ }
+ }
+ if (missing.isEmpty()) return;
+
+ StringBuilder message =
+ new StringBuilder(
+ "These configuration plugins add options to the configuration perspective but are in"
+ + " modules this generator cannot see, so the manual would not describe them:\n");
+ missing.forEach(
+ (id, module) ->
+ message.append(" ").append(id).append(" in ").append(module).append("\n"));
+ message.append(
+ "Add the module as a dependency of config-doc/pom.xml, next to the others, and they will"
+ + " be documented on the next build.");
+ throw new IllegalStateException(message.toString());
+ }
+
+ /** target/classes/META-INF/jandex.idx under some module of this tree. */
+ static boolean isModuleIndex(Path path) {
+ return path.endsWith(Paths.get("target", "classes", "META-INF", "jandex.idx"));
+ }
+
+ /** Whether the class puts any widget on the configuration perspective. */
+ static boolean hasPerspectiveWidgets(ClassInfo ci) {
+ for (FieldInfo f : ci.fields()) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ if (w != null && PARENT_ID.equals(str(w, "parentId", ""))) return true;
+ }
+ return false;
+ }
+
+ /**
+ * The .adoc files in the output directory that carry {@link #GENERATED_MARKER}. A file without it
+ * was written by hand - a README, a page someone dropped in - and this generator neither counts
+ * it nor deletes it.
+ */
+ static List<Path> generatedPages(Path outDir) throws IOException {
+ if (!Files.isDirectory(outDir)) return List.of();
+ List<Path> pages = new ArrayList<>();
+ try (Stream<Path> files = Files.list(outDir)) {
+ for (Path file : files.toList()) {
+ if (!file.getFileName().toString().endsWith(".adoc")) continue;
+ try {
+ if (Files.readString(file, StandardCharsets.UTF_8).contains(GENERATED_MARKER)) {
+ pages.add(file);
+ }
+ } catch (IOException e) {
+ // Unreadable, so not something to count on or delete.
+ }
+ }
+ }
+ return pages;
+ }
+
+ static Plugin readPlugin(ClassInfo ci, AnnotationInstance ai, Source source) {
+ List<FieldInfo> fields = new ArrayList<>();
+ for (FieldInfo f : ci.fields()) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ // No default: parentId() itself defaults to "", so falling back to the config parent
+ // here would adopt every widget that simply did not name one.
+ if (w != null && PARENT_ID.equals(str(w, "parentId", ""))) fields.add(f);
+ }
+ if (fields.isEmpty()) return null;
+
+ Properties msgs = messages(source, ci.name().packagePrefix());
+ Map<String, Object> defaults = configDefaults(ai);
+
+ List<Option> options = new ArrayList<>();
+ for (FieldInfo f : fields) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ // The configuration object is the truth where it has this setting; the annotation is the
+ // fallback for plugins that keep no such object.
+ Object fromConfig = defaults.get(f.name());
+ String dflt = fromConfig != null ? String.valueOf(fromConfig) : str(w, "defaultValue", "");
+ options.add(
+ new Option(
+ str(w, "order", "") + str(w, "id", ""),
+ translate(str(w, "label", ""), msgs),
+ translate(str(w, "toolTip", ""), msgs),
+ w.value("type") == null ? "" : w.value("type").asEnum(),
+ w.value("variables") == null || w.value("variables").asBoolean(),
+ dflt));
+ }
+ options.sort(Comparator.comparing(Option::order));
+ // The configuration perspective labels the tree with the @GuiPlugin description, so that is
+ // the name a reader is looking for on the page.
+ AnnotationInstance gui = ci.declaredAnnotation(GUI_PLUGIN);
+ String name = gui == null ? "" : translate(str(gui, "description", ""), msgs);
+ if (name.isEmpty()) {
+ name = translate(str(ai, "description", ""), msgs);
+ }
+ return new Plugin(
+ str(ai, "id", ""),
+ translate(str(ai, "description", ""), msgs),
+ name,
+ str(ai, "configKey", ""),
+ options);
+ }
+
+ /** Instantiates the plugin's configClass and reads its fields: the real, current defaults. */
+ static Map<String, Object> configDefaults(AnnotationInstance ai) {
+ AnnotationValue cc = ai.value("configClass");
+ if (cc == null) return Map.of();
+ String name = cc.asClass().name().toString();
+ if (name.equals("java.lang.Void")) return Map.of();
+ try {
+ Class<?> c = Class.forName(name);
+ Constructor<?> ctor = c.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ Object o = ctor.newInstance();
+ Map<String, Object> values = new HashMap<>();
+ for (Field f : c.getDeclaredFields()) {
+ if (Modifier.isStatic(f.getModifiers())) continue;
+ f.setAccessible(true);
+ Object v = f.get(o);
+ if (v != null
+ && !(v instanceof Collection<?> c2 && c2.isEmpty())
+ && !(v instanceof Map<?, ?> m && m.isEmpty())) {
+ values.put(f.getName(), v);
+ }
+ }
+ return values;
+ } catch (Throwable t) {
+ System.err.println(" ! could not read defaults from " + name + " : " + t);
Review Comment:
**[suggestion]** A `configClass` that fails to construct is logged and then treated as having no defaults, and the build still succeeds. The drift check only fails when the generated pages change, so a failure that happens in every build (a constructor that starts touching Hop, or a type pulled in behind the `*` exclusions) produces blank defaults that stay green once they are committed.
**Suggestion:** Fail the build when `configClass` is not `Void` and instantiation throws, instead of falling through to an empty map.
##########
config-doc/src/main/java/org/apache/hop/config/doc/ConfigDocGenerator.java:
##########
@@ -0,0 +1,526 @@
+/*
+ * 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.config.doc;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.FieldInfo;
+import org.jboss.jandex.Index;
+import org.jboss.jandex.IndexReader;
+
+/**
+ * Generates one AsciiDoc partial per configuration plugin from the annotations the configuration
+ * perspective already builds its widgets from. Build time only: it reads the Jandex indexes the
+ * build writes into every module and the message bundles next to the classes, and instantiates only
+ * the plain configuration objects, never Hop itself.
+ */
+public class ConfigDocGenerator {
+
+ static final DotName CONFIG_PLUGIN =
+ DotName.createSimple("org.apache.hop.core.config.plugin.ConfigPlugin");
+ static final DotName GUI_WIDGET =
+ DotName.createSimple("org.apache.hop.core.gui.plugin.GuiWidgetElement");
+ static final DotName GUI_PLUGIN =
+ DotName.createSimple("org.apache.hop.core.gui.plugin.GuiPlugin");
+ static final String PARENT_ID = "EnterOptionsDialog-GuiWidgetsParent";
+ static final String JANDEX_INDEX = "META-INF/jandex.idx";
+ static final String AGGREGATE = "all-plugins.adoc";
+
+ /** First line of every page this generator owns, and how it tells them from hand-written ones. */
+ static final String GENERATED_MARKER =
+ "// Generated from the @ConfigPlugin and @GuiWidgetElement annotations.";
+
+ static final String LICENSE =
+ """
+ ////
+ 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.
+ ////
+ """;
+
+ record Option(
+ String order, String label, String toolTip, String type, boolean variables, String dflt) {}
+
+ record Plugin(
+ String id, String description, String name, String configKey, List<Option> options) {}
+
+ public static void main(String[] args) throws Exception {
+ Path root = Paths.get(args[0]).toAbsolutePath().normalize();
+ Path outDir = Paths.get(args[1]).toAbsolutePath().normalize();
+
+ // Scan this program's own classpath rather than the source tree. Every module declaring a
+ // @ConfigPlugin is a declared dependency, so each one is here whether it was just built in the
+ // reactor or resolved as a jar from the local repository. Walking target/ directories instead
+ // would find nothing for the modules an incremental build left alone.
+ List<Source> sources = new ArrayList<>();
+ for (String entry : System.getProperty("java.class.path").split(File.pathSeparator)) {
+ Source source = Source.of(Paths.get(entry));
+ if (source != null) sources.add(source);
+ }
+
+ List<Plugin> plugins = new ArrayList<>();
+ for (Source source : sources) {
+ Index index;
+ try (InputStream in = source.open(JANDEX_INDEX)) {
+ if (in == null) continue;
+ index = new IndexReader(in).read();
+ } catch (Exception e) {
+ continue;
+ }
+ for (AnnotationInstance ai : index.getAnnotations(CONFIG_PLUGIN)) {
+ if (ai.target().kind() != AnnotationTarget.Kind.CLASS) continue;
+ Plugin p = readPlugin(ai.target().asClass(), ai, source);
+ if (p != null) plugins.add(p);
+ }
+ }
+ plugins.sort(Comparator.comparing(Plugin::name, String.CASE_INSENSITIVE_ORDER));
+
+ // Only the pages this generator wrote are its business. Anything else in the directory was
+ // put there by hand and is left alone, neither counted nor deleted.
+ List<Path> generatedBefore = generatedPages(outDir);
+
+ // Order matters here. An empty classpath means every plugin looks like one this generator
+ // cannot see, and the report below would then name all of them and ask for fourteen new
+ // dependencies - true in its way, and useless. "There is nothing here at all" is the more
+ // fundamental thing to have gone wrong, so it is the one to say.
+ if (plugins.isEmpty() && !generatedBefore.isEmpty()) {
+ throw new IllegalStateException(
+ "No configuration plugins found on the classpath, but "
+ + outDir.getFileName()
+ + " documents "
+ + generatedBefore.size()
+ + ". Refusing to replace those pages with nothing. This means the classpath is"
+ + " missing the modules that declare a @ConfigPlugin - build the project first.");
+ }
+
+ reportPluginsOutsideTheClasspath(root, plugins);
+
+ Files.createDirectories(outDir);
+ Set<Path> written = new HashSet<>();
+ for (Plugin p : plugins) {
+ Path file = outDir.resolve(p.id() + ".adoc");
+ Files.writeString(file, render(p), StandardCharsets.UTF_8);
+ written.add(file);
+ System.out.printf(
+ "%-34s %2d options -> %s%n", p.name(), p.options().size(), root.relativize(file));
+ }
+
+ // One page wants the lot: a section per plugin, in the order the perspective's tree shows them.
+ StringBuilder all = new StringBuilder(LICENSE);
+ all.append(GENERATED_MARKER).append("\n// Edit those, not this file.\n");
+ for (Plugin p : plugins) {
+ all.append("\n== ").append(p.name()).append("\n\n").append(body(p));
+ }
+ Path index = outDir.resolve(AGGREGATE);
+ Files.writeString(index, all.toString(), StandardCharsets.UTF_8);
+ written.add(index);
+ System.out.println("aggregate -> " + root.relativize(index));
+
+ // A plugin that was renamed or removed leaves its page behind, and the pages that include one
+ // by name would go on rendering it for good. Nothing reads a generated page that this run did
+ // not write, so it goes.
+ for (Path stale : generatedBefore) {
+ if (!written.contains(stale)) {
+ Files.delete(stale);
+ System.out.println("removed (no longer generated) -> " + root.relativize(stale));
+ }
+ }
+ System.out.println(
+ "\n"
+ + plugins.size()
+ + " configuration plugins, "
+ + plugins.stream().mapToInt(p -> p.options().size()).sum()
+ + " options");
+ }
+
+ /**
+ * Which modules a documented plugin can come from is decided by this module's dependencies, and
+ * that list is written by hand. A plugin added to a module that is not on it would be absent from
+ * the manual with nothing to say so: the page set would not change, so the drift check in CI
+ * would pass, and the option would simply never be written about.
+ *
+ * <p>So: sweep what the build has actually compiled and see whether anything turns up that the
+ * classpath did not. This is deliberately only a report - it never adds to the pages, because the
+ * set of modules on disk depends on what this particular build chose to compile, and the pages
+ * have to come out the same everywhere. An incremental build that has not compiled the new module
+ * simply finds nothing, which is why this is best-effort. A full build - every push to main is
+ * one - does find it.
+ */
+ static void reportPluginsOutsideTheClasspath(Path root, List<Plugin> documented)
+ throws IOException {
+ Set<String> known = new HashSet<>();
+ for (Plugin p : documented) {
+ known.add(p.id());
+ }
+
+ Map<String, String> missing = new TreeMap<>();
+ try (Stream<Path> walk = Files.walk(root)) {
+ for (Path index : walk.filter(ConfigDocGenerator::isModuleIndex).toList()) {
+ Path classes = index.getParent().getParent();
+ Source source = Source.of(classes);
+ if (source == null) continue;
+ Index jandex;
+ try (InputStream in = source.open(JANDEX_INDEX)) {
+ if (in == null) continue;
+ jandex = new IndexReader(in).read();
+ } catch (Exception e) {
+ continue;
+ }
+ for (AnnotationInstance ai : jandex.getAnnotations(CONFIG_PLUGIN)) {
+ if (ai.target().kind() != AnnotationTarget.Kind.CLASS) continue;
+ String id = str(ai, "id", "");
+ if (known.contains(id) || !hasPerspectiveWidgets(ai.target().asClass())) continue;
+ // target/classes/META-INF/jandex.idx -> the module directory
+ missing.put(id, root.relativize(classes.getParent().getParent()).toString());
+ }
+ }
+ }
+ if (missing.isEmpty()) return;
+
+ StringBuilder message =
+ new StringBuilder(
+ "These configuration plugins add options to the configuration perspective but are in"
+ + " modules this generator cannot see, so the manual would not describe them:\n");
+ missing.forEach(
+ (id, module) ->
+ message.append(" ").append(id).append(" in ").append(module).append("\n"));
+ message.append(
+ "Add the module as a dependency of config-doc/pom.xml, next to the others, and they will"
+ + " be documented on the next build.");
+ throw new IllegalStateException(message.toString());
+ }
+
+ /** target/classes/META-INF/jandex.idx under some module of this tree. */
+ static boolean isModuleIndex(Path path) {
+ return path.endsWith(Paths.get("target", "classes", "META-INF", "jandex.idx"));
+ }
+
+ /** Whether the class puts any widget on the configuration perspective. */
+ static boolean hasPerspectiveWidgets(ClassInfo ci) {
+ for (FieldInfo f : ci.fields()) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ if (w != null && PARENT_ID.equals(str(w, "parentId", ""))) return true;
+ }
+ return false;
+ }
+
+ /**
+ * The .adoc files in the output directory that carry {@link #GENERATED_MARKER}. A file without it
+ * was written by hand - a README, a page someone dropped in - and this generator neither counts
+ * it nor deletes it.
+ */
+ static List<Path> generatedPages(Path outDir) throws IOException {
+ if (!Files.isDirectory(outDir)) return List.of();
+ List<Path> pages = new ArrayList<>();
+ try (Stream<Path> files = Files.list(outDir)) {
+ for (Path file : files.toList()) {
+ if (!file.getFileName().toString().endsWith(".adoc")) continue;
+ try {
+ if (Files.readString(file, StandardCharsets.UTF_8).contains(GENERATED_MARKER)) {
+ pages.add(file);
+ }
+ } catch (IOException e) {
+ // Unreadable, so not something to count on or delete.
+ }
+ }
+ }
+ return pages;
+ }
+
+ static Plugin readPlugin(ClassInfo ci, AnnotationInstance ai, Source source) {
+ List<FieldInfo> fields = new ArrayList<>();
+ for (FieldInfo f : ci.fields()) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ // No default: parentId() itself defaults to "", so falling back to the config parent
+ // here would adopt every widget that simply did not name one.
+ if (w != null && PARENT_ID.equals(str(w, "parentId", ""))) fields.add(f);
+ }
+ if (fields.isEmpty()) return null;
+
+ Properties msgs = messages(source, ci.name().packagePrefix());
+ Map<String, Object> defaults = configDefaults(ai);
+
+ List<Option> options = new ArrayList<>();
+ for (FieldInfo f : fields) {
+ AnnotationInstance w = f.annotation(GUI_WIDGET);
+ // The configuration object is the truth where it has this setting; the annotation is the
+ // fallback for plugins that keep no such object.
+ Object fromConfig = defaults.get(f.name());
+ String dflt = fromConfig != null ? String.valueOf(fromConfig) : str(w, "defaultValue", "");
Review Comment:
**[suggestion]** `String.valueOf` on the config object turns `HelpOpenMode.BROWSER` into `BROWSER`. The explorer combo is filled from `getLabel()` (`External browser`, `Hop GUI tab`, `Dialog window`), which is also what `ExplorerPerspectiveConfigPlugin.getInstance()` copies into the widget. The generated Default cell therefore shows a value the dialog never displays.
**Suggestion:** If the value is an enum, prefer `getLabel()` when that method exists, otherwise `name()`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@hop.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fixes #5687 : Generate the configuration perspective option tables fr… (hop)
Posted by "mattcasters (via GitHub)" <gi...@apache.org>.
mattcasters merged PR #8706:
URL: https://github.com/apache/hop/pull/8706
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@hop.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org