Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 847981e
Merge: 0ea6dcf 23f2b9e
Author: Kris Stern <[email protected]>
Date:   Wed Feb 26 09:16:52 2025 +0800

    Merge branch 'master' into add-groups-to-command-palette

commit 0ea6dcf
Merge: c0777db 68425e2
Author: Kris Stern <[email protected]>
Date:   Wed Feb 26 01:06:33 2025 +0800

    Merge branch 'master' into add-groups-to-command-palette

commit c0777db
Merge: 1638afe c37293c
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 24 13:40:35 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 1638afe
Merge: c987a9e b97764d
Author: Jan Faracik <[email protected]>
Date:   Fri Feb 21 11:49:03 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit c987a9e
Merge: f909eec 16748f4
Author: Jan Faracik <[email protected]>
Date:   Thu Feb 20 08:17:19 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit f909eec
Merge: 85eedb7 217b0f5
Author: Jan Faracik <[email protected]>
Date:   Wed Feb 19 16:12:45 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 85eedb7
Author: Jan Faracik <[email protected]>
Date:   Wed Feb 19 16:11:24 2025 +0000

    Move to Item

commit 8f4f117
Author: Jan Faracik <[email protected]>
Date:   Wed Feb 19 15:43:50 2025 +0000

    Tighten up animations + improve contrast

commit d7b7d63
Merge: 8750f7c 4fa6127
Author: Jan Faracik <[email protected]>
Date:   Wed Feb 19 08:50:45 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 8750f7c
Merge: 7b52734 a05c33f
Author: Jan Faracik <[email protected]>
Date:   Tue Feb 18 21:42:31 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 7b52734
Merge: e2c133d 3505fb3
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 17 20:59:02 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit e2c133d
Author: Jan Faracik <[email protected]>
Date:   Sun Feb 16 18:23:14 2025 +0000

    Update require-changelog-label.yml

commit d32a61c
Author: Jan Faracik <[email protected]>
Date:   Sun Feb 16 17:11:18 2025 +0000

    Update _theme.scss

commit 42ecfca
Author: Jan Faracik <[email protected]>
Date:   Sun Feb 16 17:10:48 2025 +0000

    Rename to Items

commit cc37791
Merge: 0f1cb21 2b9d4d6
Author: Jan Faracik <[email protected]>
Date:   Sun Feb 16 17:10:25 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 0f1cb21
Merge: 04dc6cd 9474c89
Author: Jan Faracik <[email protected]>
Date:   Wed Feb 12 20:42:01 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 04dc6cd
Author: Jan Faracik <[email protected]>
Date:   Tue Feb 11 17:06:31 2025 +0000

    Reduce spacing a touch, fix icon spacing

commit 0ab3665
Merge: 7c9e172 848ac9b
Author: Jan Faracik <[email protected]>
Date:   Tue Feb 11 14:44:03 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 7c9e172
Author: Jan Faracik <[email protected]>
Date:   Tue Feb 11 11:23:27 2025 +0000

    Update Messages.properties

commit ec6a5e5
Author: Jan Faracik <[email protected]>
Date:   Tue Feb 11 08:51:16 2025 +0000

    Fix test

commit 14a6488
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 16:44:39 2025 +0000

    Tidy up

commit 46a9e56
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 16:28:15 2025 +0000

    Tidy

commit d7270b1
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 16:06:04 2025 +0000

    Tidy

commit b2da3f8
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 16:03:37 2025 +0000

    Tidy up

commit b746fba
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 15:53:53 2025 +0000

    Move to extensionpoint

commit 7827304
Merge: cac127d d03a2e1
Author: Jan Faracik <[email protected]>
Date:   Mon Feb 10 15:45:28 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit cac127d
Merge: add75bf e3e3c45
Author: Tim Jacomb <[email protected]>
Date:   Mon Jan 13 11:03:10 2025 +0000

    Merge branch 'jenkinsci:master' into add-groups-to-command-palette

commit add75bf
Author: Jan Faracik <[email protected]>
Date:   Sat Jan 11 19:02:52 2025 +0000

    Update _command-palette.scss

commit eb4073f
Author: Jan Faracik <[email protected]>
Date:   Sat Jan 11 18:59:37 2025 +0000

    Tidy up

commit 323e48f
Author: Jan Faracik <[email protected]>
Date:   Sat Jan 11 18:47:34 2025 +0000

    Update Job.java

commit 3cbdfbc
Author: Jan Faracik <[email protected]>
Date:   Sat Jan 11 18:47:13 2025 +0000

    Update _command-palette.scss

commit 8fecf0d
Merge: 428e826 331c768
Author: Jan Faracik <[email protected]>
Date:   Sat Jan 11 18:45:30 2025 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 428e826
Merge: 5657369 f1b6d31
Author: Jan Faracik <[email protected]>
Date:   Mon Dec 16 20:53:15 2024 +0000

    Merge branch 'master' into add-groups-to-command-palette

commit 5657369
Merge: 26f17a2 674d508
Author: Jan Faracik <[email protected]>
Date:   Fri Dec 13 09:46:35 2024 +0000

    Merge branch 'add-icons-to-command-palette' into add-groups-to-command-palette

commit 674d508
Merge: 809d2e6 7020e80
Author: Jan Faracik <[email protected]>
Date:   Fri Dec 13 09:42:59 2024 +0000

    Merge branch 'master' into add-icons-to-command-palette

commit 26f17a2
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 22:10:56 2024 +0000

    Update _command-palette.scss

commit 2b6ffc8
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 22:09:09 2024 +0000

    Init

commit 809d2e6
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 21:37:47 2024 +0000

    Make iconXml private, rename to icon

commit 3d45ca7
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 21:29:27 2024 +0000

    Add group field

commit 80f24cb
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 21:25:23 2024 +0000

    Init

commit 1b9faa8
Merge: d6868c9 2673844
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 21:11:56 2024 +0000

    Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/jenkins into add-icons-to-command-palette

commit d6868c9
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 21:11:41 2024 +0000

    Reword javadoc

commit 2673844
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 21:03:29 2024 +0000

    Implement IconSpec in IComputer

commit 5791010
Merge: 661f994 05ed756
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 20:23:58 2024 +0000

    Merge branch 'master' into add-icons-to-command-palette

commit 661f994
Merge: 2357020 dad5ef3
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 20:15:22 2024 +0000

    Merge branch 'refine-command-palette' into add-icons-to-command-palette

commit 2357020
Merge: 436a02b 788ae63
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 16:35:43 2024 +0000

    Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/jenkins into add-icons-to-command-palette

commit 436a02b
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 16:35:24 2024 +0000

    Add support for images

commit a3fdb3e
Merge: ea67d6a d22cc2f
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 15:27:12 2024 +0000

    Merge branch 'master' into add-icons-to-command-palette

commit 788ae63
Merge: ea67d6a d22cc2f
Author: Jan Faracik <[email protected]>
Date:   Wed Dec 11 10:00:04 2024 +0000

    Merge branch 'jenkinsci:master' into add-icons-to-command-palette

commit dad5ef3
Merge: cc63c9c d22cc2f
Author: Tim Jacomb <[email protected]>
Date:   Wed Dec 11 09:07:05 2024 +0000

    Merge branch 'master' into refine-command-palette

commit ea67d6a
Author: Jan Faracik <[email protected]>
Date:   Tue Dec 10 22:26:16 2024 +0000

    Update Search.java

commit a9aadba
Author: Jan Faracik <[email protected]>
Date:   Tue Dec 10 22:25:40 2024 +0000

    Revert "Update Search.java"

    This reverts commit 24837ea.

commit 24837ea
Author: Jan Faracik <[email protected]>
Date:   Tue Dec 10 21:59:03 2024 +0000

    Update Search.java

commit d43a8d3
Author: Jan Faracik <[email protected]>
Date:   Tue Dec 10 21:54:23 2024 +0000

    Init

commit cc63c9c
Author: Jan Faracik <[email protected]>
Date:   Tue Dec 10 21:37:09 2024 +0000

    Refine command palette
  • Loading branch information
janfaracik committed Feb 28, 2025
1 parent 7ca2e8f commit ca0176a
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 38 deletions.
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/Computer.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
import jenkins.model.IComputer;
import jenkins.model.IDisplayExecutor;
import jenkins.model.Jenkins;
import jenkins.search.SearchGroup;
import jenkins.security.ImpersonatingExecutorService;
import jenkins.security.MasterToSlaveCallable;
import jenkins.security.stapler.StaplerDispatchable;
Expand Down Expand Up @@ -1109,6 +1110,11 @@ public String getSearchUrl() {
return getUrl();
}

@Override
public SearchGroup getSearchGroup() {
return SearchGroup.get(SearchGroup.ComputerSearchGroup.class);
}

/**
* {@link RetentionStrategy} associated with this computer.
*
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/Item.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.io.IOException;
import java.util.Collection;
import jenkins.model.Jenkins;
import jenkins.search.SearchGroup;
import jenkins.util.SystemProperties;
import jenkins.util.io.OnMaster;
import org.kohsuke.stapler.StaplerRequest2;
Expand Down Expand Up @@ -249,6 +250,11 @@ default void onCreatedFromScratch() {
*/
void delete() throws IOException, InterruptedException;

@Override
default SearchGroup getSearchGroup() {
return SearchGroup.get(SearchGroup.ItemSearchGroup.class);
}

PermissionGroup PERMISSIONS = new PermissionGroup(Item.class, Messages._Item_Permissions_Title());
Permission CREATE =
new Permission(
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import jenkins.model.Loadable;
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.scm.RunWithSCM;
import jenkins.search.SearchGroup;
import jenkins.security.ImpersonatingUserDetailsService2;
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.security.UserDetailsCache;
Expand Down Expand Up @@ -284,6 +285,11 @@ public String getSearchIcon() {
return UserAvatarResolver.resolve(this, "48x48");
}

@Override
public SearchGroup getSearchGroup() {
return SearchGroup.get(SearchGroup.UserSearchGroup.class);
}

/**
* The URL of the user page.
*/
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import jenkins.model.item_category.Categories;
import jenkins.model.item_category.Category;
import jenkins.model.item_category.ItemCategory;
import jenkins.search.SearchGroup;
import jenkins.security.stapler.StaplerNotDispatchable;
import jenkins.util.xml.XMLUtils;
import jenkins.widgets.HasWidgets;
Expand Down Expand Up @@ -565,6 +566,11 @@ public String getSearchIcon() {
return "symbol-jobs";
}

@Override
public SearchGroup getSearchGroup() {
return SearchGroup.get(SearchGroup.ViewSearchGroup.class);
}

/**
* Returns the transient {@link Action}s associated with the top page.
*
Expand Down
43 changes: 33 additions & 10 deletions core/src/main/java/hudson/search/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.ExtensionComponent;
import hudson.ExtensionList;
import hudson.Util;
import hudson.util.EditDistance;
import io.jenkins.servlet.ServletExceptionWrapper;
Expand All @@ -37,12 +39,16 @@
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import jenkins.model.Jenkins;
import jenkins.search.SearchGroup;
import jenkins.security.stapler.StaplerNotDispatchable;
import jenkins.util.MemoryReductionUtil;
import jenkins.util.SystemProperties;
Expand Down Expand Up @@ -171,11 +177,26 @@ public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter

if (iconName.startsWith("symbol")) {
r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(),
Symbol.get(new SymbolRequest.Builder().withRaw(iconName).build())));
Symbol.get(new SymbolRequest.Builder().withRaw(iconName).build()), "symbol", curItem.item.getSearchGroup().getDisplayName()));
} else {
r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(), iconName, "image"));
r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(), iconName, "image", curItem.item.getSearchGroup().getDisplayName()));
}
}

// Sort results by group
ExtensionList<SearchGroup> groupsExtensionList = ExtensionList.lookup(SearchGroup.class);
List<ExtensionComponent<SearchGroup>> components = groupsExtensionList.getComponents();
Map<String, Double> searchGroupOrdinal = components.stream()
.collect(Collectors.toMap(
(k) -> k.getInstance().getDisplayName(),
ExtensionComponent::ordinal
));
r.suggestions.sort(
Comparator.comparingDouble((Item item) -> searchGroupOrdinal.getOrDefault(item.getGroup(), Double.MAX_VALUE))
.reversed()
.thenComparing(item -> item.name)
);

rsp.serveExposedBean(req, r, new ExportConfig());
}

Expand Down Expand Up @@ -279,22 +300,19 @@ public static class Item {

private final String icon;

private final String group;

public Item(String name) {
this(name, null, null);
this(name, null, null, "symbol", null);
}

public Item(String name, String url, String icon) {
public Item(String name, String url, String icon, String type, String group) {
this.name = name;
this.url = url;
this.icon = icon;
this.type = "symbol";
}

public Item(String name, String url, String icon, String type) {
this.name = name;
this.url = url;
this.icon = icon;
this.type = type;
this.group = group;
}

@Exported
Expand All @@ -311,6 +329,11 @@ public String getIcon() {
public String getType() {
return type;
}

@Exported
public String getGroup() {
return group;
}
}

private enum Mode {
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/hudson/search/SearchItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package hudson.search;

import hudson.model.Build;
import jenkins.search.SearchGroup;
import org.jenkins.ui.icon.IconSpec;

/**
Expand Down Expand Up @@ -63,6 +64,10 @@ default String getSearchIcon() {
return "symbol-search";
}

default SearchGroup getSearchGroup() {
return SearchGroup.get(SearchGroup.UnclassifiedSearchGroup.class);
}

/**
* Returns the {@link SearchIndex} to further search sub items inside this item.
*
Expand Down
73 changes: 73 additions & 0 deletions core/src/main/java/jenkins/search/SearchGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package jenkins.search;

import static jenkins.search.Messages.SearchGroup_ComputerSearchGroup_DisplayName;
import static jenkins.search.Messages.SearchGroup_ItemSearchGroup_DisplayName;
import static jenkins.search.Messages.SearchGroup_UnclassifiedSearchGroup_DisplayName;
import static jenkins.search.Messages.SearchGroup_UserSearchGroup_DisplayName;
import static jenkins.search.Messages.SearchGroup_ViewSearchGroup_DisplayName;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.ModelObject;

public interface SearchGroup extends ExtensionPoint, ModelObject {

static ExtensionList<SearchGroup> all() {
return ExtensionList.lookup(SearchGroup.class);
}

static @NonNull <T extends SearchGroup> T get(Class<T> type) {
T category = all().get(type);
if (category == null) {
throw new AssertionError("Group not found. It seems the " + type + " is not annotated with @Extension and so not registered");
}
return category;
}

@Extension(ordinal = -1)
class UnclassifiedSearchGroup implements SearchGroup {

@Override
public String getDisplayName() {
return SearchGroup_UnclassifiedSearchGroup_DisplayName();
}
}

@Extension(ordinal = 999)
class ItemSearchGroup implements SearchGroup {

@Override
public String getDisplayName() {
return SearchGroup_ItemSearchGroup_DisplayName();
}
}

@Extension
class ComputerSearchGroup implements SearchGroup {

@Override
public String getDisplayName() {
return SearchGroup_ComputerSearchGroup_DisplayName();
}
}

@Extension
class ViewSearchGroup implements SearchGroup {

@Override
public String getDisplayName() {
return SearchGroup_ViewSearchGroup_DisplayName();
}
}

@Extension
class UserSearchGroup implements SearchGroup {

@Override
public String getDisplayName() {
return SearchGroup_UserSearchGroup_DisplayName();
}
}
}
27 changes: 27 additions & 0 deletions core/src/main/resources/jenkins/search/Messages.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# The MIT License
#
# Copyright (c) 2025 Jan Faracik
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

SearchGroup.UnclassifiedSearchGroup.DisplayName=Other
SearchGroup.ItemSearchGroup.DisplayName=Items
SearchGroup.ComputerSearchGroup.DisplayName=Nodes
SearchGroup.ViewSearchGroup.DisplayName=Views
SearchGroup.UserSearchGroup.DisplayName=Users
1 change: 1 addition & 0 deletions src/main/js/components/command-palette/datasources.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const JenkinsSearchSource = {
type: e.type,
label: e.name,
url: correctAddress(e.url),
group: e.group,
}),
);
}),
Expand Down
23 changes: 18 additions & 5 deletions src/main/js/components/command-palette/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as Symbols from "./symbols";
import makeKeyboardNavigable from "@/util/keyboard";
import { xmlEscape } from "@/util/security";
import { createElementFromHtml } from "@/util/dom";
import { groupResultsByCategory } from "@/components/command-palette/utils";

const datasources = [JenkinsSearchSource];

Expand Down Expand Up @@ -68,6 +69,7 @@ function init() {
label: i18n.dataset.getHelp,
url: document.querySelector("body").dataset.searchHelpUrl,
isExternal: true,
group: null,
}),
]);
} else {
Expand All @@ -77,15 +79,26 @@ function init() {
}

results.then((results) => {
results = groupResultsByCategory(results);

// Clear current search results
searchResults.innerHTML = "";

if (query.length === 0 || Object.keys(results).length > 0) {
results.forEach(function (obj) {
const link = createElementFromHtml(obj.render());
link.addEventListener("mouseenter", (e) => itemMouseEnter(e));
searchResults.append(link);
});
for (const [group, items] of Object.entries(results)) {
if (group !== "null") {
const heading = document.createElement("p");
heading.className = "jenkins-command-palette__results__heading";
heading.innerText = group;
searchResults.append(heading);
}

items.forEach(function (obj) {
const link = createElementFromHtml(obj.render());
link.addEventListener("mouseenter", (e) => itemMouseEnter(e));
searchResults.append(link);
});
}

updateSelectedItem(0);
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/main/js/components/command-palette/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { xmlEscape } from "@/util/security";
* @param {string} params.label
* @param {'symbol' | 'image'} params.type
* @param {string} params.url
* @param {string | null} params.group
* @param {boolean | undefined} params.isExternal
*/
export function LinkResult(params) {
return {
label: params.label,
url: params.url,
group: params.group,
render: () => {
return `<a class="jenkins-command-palette__results__item" href="${xmlEscape(
params.url,
Expand Down
13 changes: 13 additions & 0 deletions src/main/js/components/command-palette/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Group results by 'group' field into a map
*/
export function groupResultsByCategory(array) {
return array.reduce((hash, obj) => {
if (obj.group === undefined) {
return hash;
}
return Object.assign(hash, {
[obj.group]: (hash[obj.group] || []).concat(obj),
});
}, {});
}
Loading

0 comments on commit ca0176a

Please sign in to comment.