Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add icons to Command Palette #10049

Merged
merged 23 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cc63c9c
Refine command palette
janfaracik Dec 10, 2024
d43a8d3
Init
janfaracik Dec 10, 2024
24837ea
Update Search.java
janfaracik Dec 10, 2024
a9aadba
Revert "Update Search.java"
janfaracik Dec 10, 2024
ea67d6a
Update Search.java
janfaracik Dec 10, 2024
dad5ef3
Merge branch 'master' into refine-command-palette
timja Dec 11, 2024
788ae63
Merge branch 'jenkinsci:master' into add-icons-to-command-palette
janfaracik Dec 11, 2024
a3fdb3e
Merge branch 'master' into add-icons-to-command-palette
timja Dec 11, 2024
436a02b
Add support for images
timja Dec 11, 2024
2357020
Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/…
timja Dec 11, 2024
661f994
Merge branch 'refine-command-palette' into add-icons-to-command-palette
janfaracik Dec 11, 2024
5791010
Merge branch 'master' into add-icons-to-command-palette
janfaracik Dec 11, 2024
2673844
Implement IconSpec in IComputer
janfaracik Dec 11, 2024
d6868c9
Reword javadoc
timja Dec 11, 2024
1b9faa8
Merge branch 'add-icons-to-command-palette' of github.com:janfaracik/…
timja Dec 11, 2024
809d2e6
Make iconXml private, rename to icon
janfaracik Dec 11, 2024
674d508
Merge branch 'master' into add-icons-to-command-palette
janfaracik Dec 13, 2024
e65ce20
Update AbstractProjectTest.java
janfaracik Dec 13, 2024
b3347e2
Trigger Build
janfaracik Dec 14, 2024
489bdec
Merge branch 'master' into add-icons-to-command-palette
janfaracik Dec 14, 2024
b3d636e
Trigger Build
janfaracik Dec 15, 2024
be2a4e7
Trigger Build
janfaracik Dec 15, 2024
0c3fee6
Merge branch 'master' into add-icons-to-command-palette
timja Dec 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/main/java/hudson/model/Job.java
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,11 @@ public boolean supportsLogRotator() {
return true;
}

@Override
public String getSearchIcon() {
return "symbol-status-" + this.getIconColor().getIconName();
}

@Override
protected SearchIndexBuilder makeSearchIndex() {
return super.makeSearchIndex().add(new SearchIndex() {
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 @@ -44,6 +44,7 @@
import hudson.security.AccessControlled;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException2;
import hudson.tasks.UserAvatarResolver;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.util.XStream2;
Expand Down Expand Up @@ -278,6 +279,11 @@
return "/user/" + Util.rawEncode(idStrategy().keyFor(id));
}

@Override
public String getSearchIcon() {
return UserAvatarResolver.resolve(this, "48x48");

Check warning on line 284 in core/src/main/java/hudson/model/User.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 284 is not covered by tests
}

/**
* The URL of the user page.
*/
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/hudson/model/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,11 @@
return getUrl();
}

@Override
public String getSearchIcon() {
return "symbol-jobs";

Check warning on line 565 in core/src/main/java/hudson/model/View.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 565 is not covered by tests
}

/**
* Returns the transient {@link Action}s associated with the top page.
*
Expand Down
49 changes: 44 additions & 5 deletions core/src/main/java/hudson/search/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
import jenkins.security.stapler.StaplerNotDispatchable;
import jenkins.util.MemoryReductionUtil;
import jenkins.util.SystemProperties;
import org.jenkins.ui.symbol.Symbol;
import org.jenkins.ui.symbol.SymbolRequest;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Ancestor;
Expand All @@ -56,6 +58,7 @@
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.export.DataWriter;
import org.kohsuke.stapler.export.ExportConfig;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Flavor;
Expand Down Expand Up @@ -157,10 +160,23 @@
*/
public void doSuggest(StaplerRequest2 req, StaplerResponse2 rsp, @QueryParameter String query) throws IOException, ServletException {
Result r = new Result();
for (SuggestedItem item : getSuggestions(req, query))
r.suggestions.add(new Item(item.getPath(), item.getUrl()));
for (SuggestedItem curItem : getSuggestions(req, query)) {
String iconName = curItem.item.getSearchIcon();
Copy link
Member

@timja timja Dec 15, 2024

Choose a reason for hiding this comment

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

(nonblocking as pre-existing, but can potentially be enhanced in a followup)

One pre-existing issue is that items with a display name are found twice, (even when the actual name is nothing alike)

image

But the display name version isn't an instanceof Item, which means it just gets the default icon.

Ideally it wouldn't report twice or at least it would provide the right item.

See pre-existing screenshot from ci.jenkins.io:
image


rsp.serveExposedBean(req, r, Flavor.JSON);
if (iconName == null ||

Check warning on line 166 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 166 is only partially covered, one branch is missing
(!iconName.startsWith("symbol-") && !iconName.startsWith("http"))

Check warning on line 167 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 167 is only partially covered, 3 branches are missing
) {
iconName = "symbol-search";

Check warning on line 169 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 169 is not covered by tests
}

if (iconName.startsWith("symbol")) {

Check warning on line 172 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 172 is only partially covered, one branch is missing
r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(),
Symbol.get(new SymbolRequest.Builder().withRaw(iconName).build())));
} else {
r.suggestions.add(new Item(curItem.getPath(), curItem.getUrl(), iconName, "image"));

Check warning on line 176 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 176 is not covered by tests
}
}
rsp.serveExposedBean(req, r, new ExportConfig());
}

/**
Expand Down Expand Up @@ -259,19 +275,42 @@

private final String url;

private final String type;

private final String icon;

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

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

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

Check warning on line 297 in core/src/main/java/hudson/search/Search.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 293-297 are not covered by tests
}

@Exported
public String getUrl() {
return url;
}

@Exported
public String getIcon() {
return icon;
}

@Exported
public String getType() {
return type;
}
}

private enum Mode {
Expand Down
9 changes: 9 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 org.jenkins.ui.icon.IconSpec;

/**
* Represents an item reachable from {@link SearchIndex}.
Expand Down Expand Up @@ -54,6 +55,14 @@

String getSearchUrl();

default String getSearchIcon() {
if (this instanceof IconSpec) {

Check warning on line 59 in core/src/main/java/hudson/search/SearchItem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 59 is only partially covered, one branch is missing
return ((IconSpec) this).getIconClassName();

Check warning on line 60 in core/src/main/java/hudson/search/SearchItem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 60 is not covered by tests
}

return "symbol-search";
}

/**
* Returns the {@link SearchIndex} to further search sub items inside this item.
*
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/jenkins/model/IComputer.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import jenkins.agents.IOfflineCause;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.jenkins.ui.icon.IconSpec;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;

Expand All @@ -44,7 +45,7 @@
* @since 2.480
*/
@Restricted(Beta.class)
public interface IComputer extends AccessControlled {
public interface IComputer extends AccessControlled, IconSpec {
/**
* Returns {@link Node#getNodeName() the name of the node}.
*/
Expand Down
27 changes: 24 additions & 3 deletions core/src/main/java/jenkins/model/Jenkins.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
import hudson.scm.RepositoryBrowser;
import hudson.scm.SCM;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.search.SearchItem;
import hudson.security.ACL;
Expand Down Expand Up @@ -2345,9 +2346,29 @@
@Override
public SearchIndexBuilder makeSearchIndex() {
SearchIndexBuilder builder = super.makeSearchIndex();
if (hasPermission(ADMINISTER)) {
builder.add("manage", Messages.ManageJenkinsAction_DisplayName());
}

this.actions.stream().filter(e -> e.getIconFileName() != null).forEach(action -> builder.add(new SearchItem() {
@Override
public String getSearchName() {
return action.getDisplayName();
}

@Override
public String getSearchUrl() {
return action.getUrlName();
}

@Override
public String getSearchIcon() {
return action.getIconFileName();
}

@Override
public SearchIndex getSearchIndex() {
return SearchIndex.EMPTY;

Check warning on line 2368 in core/src/main/java/jenkins/model/Jenkins.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 2358-2368 are not covered by tests
}
}));

builder.add(new CollectionSearchIndex<TopLevelItem>() {
@Override
protected SearchItem get(String key) { return getItemByFullName(key, TopLevelItem.class); }
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/java/org/jenkins/ui/icon/IconSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
/**
* Icon Specification.
* <br>
* Plugin extension points that implement/extend Action/ManagementLink should
* also implement this interface.
* If your class provides an icon spec you should implement this interface.
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
* @since 2.0
Expand Down
4 changes: 2 additions & 2 deletions src/main/js/components/command-palette/datasources.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { LinkResult } from "./models";
import Search from "@/api/search";
import * as Symbols from "./symbols";

export const JenkinsSearchSource = {
execute(query) {
Expand All @@ -18,7 +17,8 @@ export const JenkinsSearchSource = {
rsp.json().then((data) => {
return data["suggestions"].slice().map((e) =>
LinkResult({
icon: Symbols.SEARCH,
icon: e.icon,
type: e.type,
label: e.name,
url: correctAddress(e.url),
}),
Expand Down
1 change: 1 addition & 0 deletions src/main/js/components/command-palette/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function init() {
results = Promise.all([
LinkResult({
icon: Symbols.HELP,
type: "symbol",
label: i18n.dataset.getHelp,
url: headerCommandPaletteButton.dataset.searchHelpUrl,
isExternal: true,
Expand Down
6 changes: 3 additions & 3 deletions src/main/js/components/command-palette/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { xmlEscape } from "@/util/security";
* @param {Object} params
* @param {string} params.icon
* @param {string} params.label
* @param {'symbol' | 'image'} params.type
* @param {string} params.url
* @param {boolean | undefined} params.isExternal
*/
Expand All @@ -16,9 +17,8 @@ export function LinkResult(params) {
return `<a class="jenkins-command-palette__results__item" href="${xmlEscape(
params.url,
)}">
<div class="jenkins-command-palette__results__item__icon">${
params.icon
}</div>
${params.type === "image" ? `<img alt="${xmlEscape(params.label)}" class="jenkins-command-palette__results__item__icon" src="${params.icon}" />` : ""}
${params.type !== "image" ? `<div class="jenkins-command-palette__results__item__icon">${params.icon}</div>` : ""}
${xmlEscape(params.label)}
${params.isExternal ? Symbols.EXTERNAL_LINK : ""}
</a>`;
Expand Down
1 change: 0 additions & 1 deletion src/main/js/components/command-palette/symbols.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/main/scss/components/_command-palette.scss
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@

svg,
img {
width: 1.125rem;
height: 1.125rem;
width: 1.25rem;
height: 1.25rem;
}
}

Expand Down
2 changes: 2 additions & 0 deletions test/src/test/java/hudson/model/AbstractProjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@ private void testAutoCompleteResponse(JSONObject responseBody, String... project
JSONObject o = new JSONObject();
o.put("name", p);
o.put("url", JSONObject.fromObject(null));
o.put("icon", JSONObject.fromObject(null));
o.put("type", "symbol");
expected.add(o);
}
assertThat(suggestions.containsAll(expected), is(true));
Expand Down
5 changes: 5 additions & 0 deletions war/src/main/resources/images/symbols/jobs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading