Skip to content

Modernize the UI and add preselect URLs#681

Open
ironcerocloudbees wants to merge 14 commits into
jenkinsci:masterfrom
ironcerocloudbees:modern_ui
Open

Modernize the UI and add preselect URLs#681
ironcerocloudbees wants to merge 14 commits into
jenkinsci:masterfrom
ironcerocloudbees:modern_ui

Conversation

@ironcerocloudbees

Copy link
Copy Markdown
Contributor

This is an attempt to enhance the Support Bundle Generation view. The improvements introduced are:

Moving the list of options from the main page to a pop-up/widget.

Users don't always need to mark or unmark any options if they just want to generate a default support bundle. Moving the list to a pop-up or widget allows us to add more elements to manage the checked options in the main view (see next improvement sections).

To open the widget we will need to click on Select options:

SCR-20250828-mckr

The new widget shows the following:

SCR-20250828-mczm

Please pay attention to the code displayed in the bottom-right corner. This is a HASH generated from the current selected (checked) options. Please refer to the "Custom options" section for more information about this new element.

You can close the widget by clicking on Close or pressing the Esc key.

Choose a general set of options

Selecting which options should be checked can be complex for standards users, so it may be a good idea to have a dropdown element with some standard options (such as configuration files, performance, etc.). For the moment, only three options were added (default, default + configuration files, and performance). As soon as the user clicks or selects one general option in this dropdown, the options will be checked in the widget (by JS).

SCR-20250828-mgck

Custom options

This could be the most interesting part of this development.

Each option will have an ID in the list of options. Anytime that a user check or uncheck any option in the widget a HASH value will be calculated in base on the current selected options. This HASH will appear in two locations: in the bottom-right corner of the widget, and in the input textbox under the Choose a general set of options.

SCR-20250828-mhsg

Selecting the Custom value in the Choose a general set of options dropdown, the textbox for the custom value will appear. If you type a HASH value in this textbox and press the Enter key (or click in the Apply button), the options in the widget will be selected according to this HASH value. After that, we could optionally click on Select options and alter this set of select options.

There is an issue with plugins that extend the Component class. The elements shown in the widget (and originally on that page) are classes that extend the Component class and satisfy some requirements. Those classes can be provided by any plugin (not only the support-core-plugin). This question makes the management of this HASH so hard (since we cannot ensure that all the child components will implement the getHash method (inherited from the Component class). Therefore, the decision was made to disregard the child components that would not correctly implement the getHash method. By doing that, we can share a HASH code that we can ensure will work.

Testing done

The development was tested in three different ways:

  1. A Junit test was created for the getHash method of the child components.
  2. The plugin was deployed in Jenkins version 2.479.3 and passed the basic integration tests:
    1. Selecting all the general options and confirming that the options were checked correctly in the widget.
    2. Selecting the Custom general options and confirming that the correct options were checked with different known HASH codes.
    3. Checking and unckecking different options in the widget and confirming that the generated HASH was correct.
    4. Confirming the usage of events (like the Esc for the widget or the Enter for the custom textbox).
    5. Generating a support bundle and confirming that the options checked in the widget were included in the file.

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@ironcerocloudbees ironcerocloudbees requested a review from a team as a code owner August 28, 2025 12:05

@PierreBtz PierreBtz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a stab at this. Having profiles to simplify component selection has been a long awaited feature!

I didn't look in details yet, but I have a couple of questions:

  • it looks like you chose to go with a client only implementation, which means that we won't have this feature available in the API for use by a external tool. Did you consider implementing the feature server side?

  • I'm worried about delegating the hash implementation to all the plugin developers offering a Component, which excludes important components from this new feature (and might be seen by end users as a bug in this plugin). Maybe we could thing of another hash implementation that would work out of the box for anybody (something based on the className of the component maybe?).

Comment thread src/main/java/com/cloudbees/jenkins/support/api/Component.java Outdated
Comment thread src/main/resources/com/cloudbees/jenkins/support/Messages.properties Outdated
@ironcerocloudbees

Copy link
Copy Markdown
Contributor Author

Hi Pierre, thank you for taking care of that.

  • it looks like you chose to go with a client only implementation, which means that we won't have this feature available in the API for use by a external tool. Did you consider implementing the feature server side?

I did not consider it. The development of having a server-side should be easy. Initially, we can have an input hidden textbox in the form that the server side will ignore if the rest of the names/checkboxes are present in the data (which will mean that we are coming from the UI), and it will be treated in case the other elements are missing (so, we can use this value for APIs).

  • I'm worried about delegating the hash implementation to all the plugin developers offering a Component, which excludes important components from this new feature (and might be seen by end users as a bug in this plugin). Maybe we could thing of another hash implementation that would work out of the box for anybody (something based on the className of the component maybe?).

That is an excellent question. Cristian and I have been discussing this question in depth. AFAIU, including the Component offered by other plugins will make it hard to prepare and deliver (to the users) a pre-setup code/hash/config. Let me try to explain it:

The main goal of this development (apart from making the generation of support bundles easier for the users) is to have some previously discovered codes/hashes. Once a user has a problem, we (the community) can share one of those codes/hashes with the user to accurately collect the information that we need to help with the troubleshooting. If the user has a set of plugins (that we don't know yet until having the first support bundle) that include new Components, we will not be able to share a code/hash that includes those new options.

In the current approach (ignoring the new Components), we can use codes/hashes with a good number of options (all the elements from the support-core-plugin and from other well-known plugins like pipelines, etc), and ignore the options for the custom ones.

It is true that if a developer introduces a new component and uses the same hash that already exists in the support-core-plugin, the ui will fail and we will not be able to use the new feature. I will consider ways to avoid such issues.

@ironcerocloudbees

Copy link
Copy Markdown
Contributor Author

Hi @PierreBtz,

I have been reviewing the options for the "I'm worried about delegating the hash implementation to all the plugin developers offering a Component" problem. I do believe the best option to avoid this problem (other developers could break the new system making wrong implementation of the getHash method) is to have a list in the support-core-plugin. We will have the Class’s Fully Qualified Name assigned to a part of the hash for each component. So, we can manage which components will be included in the code/hash. We can include a comment in the Component class to advise the developers to make a PR in support-core-plugin to include the new components in the list.

What do you think?

jglick
jglick previously requested changes Sep 4, 2025

@jglick jglick left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎 on the hard-coded list of components from a modularity perspective. Components provided by other plugins should be treated as first class.

The custom options and hash system seems like a weird UX to me. Why not just remember the last-selected set of components in a UserProperty and use that as a default, unless you reset to a predefined profile? https://issues.jenkins.io/browse/JENKINS-20904 681606c#commitcomment-4724976

Also see this CloudBees-internal issue.

@jglick

jglick commented Sep 4, 2025

Copy link
Copy Markdown
Member

Also I wonder if we need the dropdown and the popup, or if we could just have buttons to select all/none of the components generally, or within one of the categories. That would be make it easy to collect all components pertaining to agents, for example. Plus a button to reset to defaults.

@PierreBtz

Copy link
Copy Markdown
Contributor

@ironcerocloudbees sorry for the delay. My suggestion to get going would be to split this development in two since you are trying to tackle two issues at once:

  1. Introduction of profiles

The current architecture is problematic, the plugin shouldn't have knowledge of downstream components.

I tend to agree with Jesse's approach of storing custom profiles as a UserProperty (or if want to keep things on the front end, a cookie maybe?).

One thing that this approach doesn't allow is custom profile sharing (I don't know if it's a requirement of yours or not?). If it is, you could maybe provide an url fragment to share with the component selection appearing as query parameters. Note that to keep things simple, this can be delivered independently on a subsequent PR.

  1. a UX redesign (depends on 1 but can be delivered after)

No strong opinion on this I'm no UX designer (and maybe we could ask for the opinion of one...). I personally like that the main view is not polluted by a long list of components anymore.

@jglick

jglick commented Sep 5, 2025

Copy link
Copy Markdown
Member

an url fragment to share with the component selection appearing as query parameters

I like this idea. There is already a Component.id or similar that could be used. (If a full list of literal ids is too long for a query parameter, you could just use a short hash of each id to include, like ?components=468de0a,223ff0a,cc320ab or the like. Assuming there are no hash collisions, unlikely at such a small scale, this is safe because the server can look up a list of all known ids and see which ones which were selected by hash.)

Comment thread src/main/java/com/cloudbees/jenkins/support/api/Component.java Outdated
Comment thread src/main/resources/com/cloudbees/jenkins/support/SupportAction/index.jelly Outdated
@jglick jglick dismissed their stale review September 17, 2025 15:20

No longer hard-coding components that I can see.

@jglick jglick requested a review from a team September 17, 2025 15:20

@Dohbedoh Dohbedoh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution and sorry for reviewing so late...
Overall it looks OK to me but there is still some work to it until we can merge. I believe the Component ID approach is best due to the plugin extensions. FWIW I think the UI / CLI could be adapted to support selecting a profile.

Also I wonder if we need the dropdown and the popup, or if we could just have buttons to select all/none of the components generally, or within one of the categories. That would be make it easy to collect all components pertaining to agents, for example. Plus a button to reset to defaults.

I think this was general idea we had the various time we discussed about this. I was imagining a reactive grid component with profile selection and even single component content download. But my expertise in UI / fronted is limited. The feature in this PR is still a step forward towards this and that's great.

Couple of points I thin kneed to be addressed:

  • When testing this PR, I was looking for the list of components that each of those options would pre-select. Which I could not get. I thought that the "Select components" would show me the list for the option I just picked but actually it doesn't. I wish I could see what components are included.

  • The default selection is changed. There is a method isSelectedByDefault() that should be integrated in the "Default" options to preserve it. NOTE: the review of the defaut selection is a topic in itself. There's been a growing lists of components that are selected by default and that need to be reviewed. Maybe as part of a different PR.

  • Some UI tests will need to be updated I think.

NOTE: I am also wondering if the options list should be made extensible. If other plugin could contribute a profile for example.

Comment on lines +139 to +186
public String getApplyCustomChanges() {
return Messages.SupportPlugin_ApplyCustomChanges();
}

public String getChooseGeneralComponent() {
return Messages.SupportPlugin_ChooseGeneralComponent();
}

public String getChooseGeneralComponentApplied() {
return Messages.SupportPlugin_ChooseGeneralComponentApplied();
}

public String getChooseGeneralComponentApplying() {
return Messages.SupportPlugin_ChooseGeneralComponentApplying();
}

public String getChooseComponents() {
return Messages.SupportPlugin_ChooseComponents();
}

public String getClose() {
return Messages.SupportPlugin_Close();
}

public String getGenerateSupportBundle() {
return Messages.SupportPlugin_GenerateSupportBundle();
}

public String getGenericComponentConfigurationFiles() {
return Messages.SupportPlugin_GenericComponent_ConfigurationFiles();
}

public String getGenericComponentCopy() {
return Messages.SupportPlugin_GenericComponent_Copy();
}

public String getGenericComponentCustom() {
return Messages.SupportPlugin_GenericComponent_Custom();
}

public String getGenericComponentDefault() {
return Messages.SupportPlugin_GenericComponent_Default();
}

public String getGenericComponentPerformanceData() {
return Messages.SupportPlugin_GenericComponent_PerformanceData();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since those are only used in Jelly and not dynamically set, they could just be added to the index.properties maybe.. For the description of the Options (enum) see below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

I didn't know that we used to use the index.properties for "static" values. I already move the static ones to the index.properties.

Thanks for the tip.

Comment on lines +668 to +672
public enum PreChooseOptions {
Default,
ConfigurationFiles,
PerformanceData
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that we enrich the enum to hold the internationalization. Also seems to me that custom should be included in the options ?:

    public enum PreChooseOptions {
        Default(Messages._SupportPlugin_GenericComponent_Default()),
        ConfigurationFiles(Messages._SupportPlugin_GenericComponent_ConfigurationFiles()),
        PerformanceData(Messages._SupportPlugin_GenericComponent_PerformanceData()),
        Custom(Messages._SupportPlugin_GenericComponent_Custom());

        private final Localizable description;

        PreChooseOptions(Localizable description) {
            this.description = description;
        }

        public Localizable getDescription() {
            return description;
        }
    }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

I created the localization directly in the enum. Previously, I already had it using a generic method in the SupportAction class. However, this implementation looks more elegant to me :)

Comment on lines +659 to +662
public String getPreChooseOptionsMessage(PreChooseOptions preChooseOptions) {
ResourceBundleHolder holder = ResourceBundleHolder.get(Messages.class);
return holder.format("SupportPlugin_GenericComponent_" + preChooseOptions.name());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be needed if the enum holds the description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +179 to +185
public SupportAction.PreChooseOptions[] getDefautlPreChooseOptions(){
return new SupportAction.PreChooseOptions[0];
}

public Boolean checkDefaultPreChooseOptions(SupportAction.PreChooseOptions preChooseOptions) {
return Arrays.stream(getDefautlPreChooseOptions()).anyMatch(it -> {return it == preChooseOptions;});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a way, it might be simpler to just have the boolean isApplicable(SupportAction.PreChooseOptions preChooseOptions) and for component to just override it. Otherwise we are doing an Arrays.stream for every single component.

Now as far as I understand, we are actually changing the default selection here. Some components are selected by defaults - see isSelectedByDefault() - so in the case of the Default option we could use return isSelectedByDefault() and for other cases let the actual component override the method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done,

I changed the stream/array for a method (boolean isApplicable(SupportAction.PreChooseOptions preChooseOptions) that gets the information from the isSelectedByDefault(), and we can easily override it if we need to add the component to a performance data or any other pre-choose options set.

Comment on lines +6 to +7
You can also select the <i>Custom</i> component and type a hexadecimal value in the auxiliary text box if you want to check some specific components (the components will be checked depending on this hexadecimal value). To get an example of the hexadecimal value, you click in the <i>Select components</i>> button, mark the components that you want and copy the value that you can find in the bottom and right corne of the widget.
</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph will need to be updated if we don't use the hash

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ironcerocloudbees

Copy link
Copy Markdown
Contributor Author

Hi @Dohbedoh,

Thank you so much for your review.

When testing this PR, I was looking for the list of components that each of those options would pre-select. Which I could not get. I thought that the "Select components" would show me the list for the option I just picked but actually it doesn't. I wish I could see what components are included.

This behavior sounds like a bug. When we choose an element in the dropdown menu (with Default, Default with configuration files, or Performance data), a JS function will check and uncheck the components in the form. After choosing the element, clicking the Select component button will display the components currently selected in the form.
I thoroughly tested it in various browsers, and the JS function did not fail.

Could you please check if you have any JS errors in the browser console?

The default selection is changed. There is a method isSelectedByDefault() that should be integrated in the "Default" options to preserve it. NOTE: the review of the defaut selection is a topic in itself. There's been a growing lists of components that are selected by default and that need to be reviewed. Maybe as part of a different PR.

That is a really good idea. I will modify it to use the isSelectedByDefault() method in case of Default option.

Some UI tests will need to be updated I think.

Are you considering additional tests for the UI changes? Or did you mean revisiting the UI tests with the new UI changes in the PR?

NOTE: I am also wondering if the options list should be made extensible. If other plugin could contribute a profile for example.

Are you meaning the predefined option lists (aka Default, Default with configuration files, Performance data, Custom)? If so, I will investigate how to accomplish it, and we could include it in a different PR (once this one is merged).

For the rest of the comments/tips/etc, I will update the sub-conversations.

Kind regards,

return false;
}

public SupportAction.PreChooseOptions[] getDefautlPreChooseOptions(){

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread src/main/java/com/cloudbees/jenkins/support/api/Component.java Outdated
@jglick jglick changed the title Modernizing the UI and adding a HASH Modernize the UI and add preselect URLs Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants