{
+ this._categories = jsonRpcResponse.result;
+ this._categories.push({name:'Uncategorised', id: 'uncategorised'});
+ });
+
+ if(!this._extensions){
+ this.hotReload();
+ }
+ }
+
+ hotReload(){
+ this.jsonRpc.getInstallableExtensions().then(jsonRpcResponse => {
+ this._extensions = jsonRpcResponse.result;
+ this._filteredExtensions = this._extensions;
+ });
+ }
+
+ render() {
+ if(this._filteredExtensions){
+ return html`
+ ${this._renderFilterBar()}
+ ${this._renderGrid()}
+
`;
+ }else{
+ return html`
+
+
+
+
+
`;
+ }
+ }
+
+ _renderGrid(){
+ return html` {
+ const prop = event.detail.value;
+ this._detailsOpenedItem = prop ? [prop] : [];
+ }}"
+ ${gridRowDetailsRenderer(this._descriptionRenderer, [])}>
+
+
+ `;
+ }
+
+ _renderFilterBar(){
+ return html`
+ this._filterTextChanged(e)}">
+
+ ${this._filteredExtensions.length}
+
+ ${this._renderCategoryDropdown()}
+
`;
+
+ }
+
+ _renderCategoryDropdown(){
+ if(this._categories){
+ return html` this._filterCategoryChanged(e)}"
+ clear-button-visible
+ >`;
+ }
+ }
+
+ _filterCategoryChanged(e){
+ this._filteredCategory = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterTextChanged(e) {
+ this._filteredValue = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterGrid(){
+ this._filteredExtensions = this._extensions.filter((prop) => {
+ if(this._filteredValue && this._filteredValue !== '' && this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByTerm(prop) && this._filterByCategory(prop);
+ }else if(this._filteredValue && this._filteredValue !== ''){
+ return this._filterByTerm(prop);
+ }else if(this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByCategory(prop);
+ }else{
+ return true;
+ }
+ });
+ }
+
+ _filterByTerm(prop){
+ if(prop.metadata && prop.metadata.keywords){
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue) || prop.metadata.keywords.includes(this._filteredValue);
+ }else{
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue);
+ }
+ }
+
+ _filterByCategory(prop){
+ if(prop.metadata && prop.metadata.categories){
+ return prop.metadata.categories.includes(this._filteredCategory);
+ }else if(this._filteredCategory === "uncategorised"){
+ return true;
+ }else {
+ return false;
+ }
+ }
+
+ _match(value, term) {
+ if (! value) {
+ return false;
+ }
+
+ return value.toLowerCase().includes(term.toLowerCase());
+ }
+
+ _descriptionRenderer(prop) {
+
+ return html`
+
+ Artifact: ${prop.artifact.groupId}:${prop.artifact.artifactId}
+ Version: ${prop.artifact.version}
+ ${this._renderIsPlatform(prop)}
+ ${this._renderMetadata1(prop)}
+
+
+ ${this._renderMetadata2(prop)}
+
+
+ this._install(prop)}">
+
+ Add Extension
+
+ `;
+ }
+
+ _renderMetadata1(prop){
+ if(prop.metadata){
+ return html`${this._renderGuide(prop.metadata)}
+ ${this._renderScmUrl(prop.metadata)}
+ ${this._renderStatus(prop.metadata)}
+ ${this._renderMinJavaVersion(prop.metadata)}`;
+ }
+ }
+
+ _renderIsPlatform(prop){
+ if (prop.origins && prop.origins.some(str => str.startsWith("io.quarkus:quarkus-bom-quarkus-platform"))){
+ return html`Platform: `;
+ } else {
+ return html`Platform: `;
+ }
+ }
+
+ _renderGuide(metadata){
+ if (metadata.guide){
+ return html`Guide: ${metadata.guide}`;
+ }
+ }
+
+ _renderScmUrl(metadata){
+ if (metadata['scm-url']){
+ return html`SCM: ${metadata['scm-url']}`;
+ }
+ }
+
+ _renderStatus(metadata){
+ if(metadata.status){
+ return html`Status: ${metadata.status.toUpperCase()}`;
+ }
+ }
+
+ _renderMinJavaVersion(metadata){
+ if(metadata['minimum-java-version']){
+ return html`Minimum Java version: ${metadata['minimum-java-version']}`;
+ }
+ }
+
+ _renderMetadata2(prop){
+ if(prop.metadata){
+ return html`${this._renderKeywords(prop.metadata)}
+ ${this._renderCategories(prop.metadata)}
+ ${this._renderExtensionDependencies(prop.metadata)}`;
+ }
+ }
+
+
+
+
+
+ _statusLevel(s){
+ if(s === "stable") {
+ return "success";
+ } else if(s === "experimental") {
+ return "warning";
+ } else if(s === "preview") {
+ return "contrast";
+ }
+ return null;
+ }
+
+ _renderCategories(metadata){
+ if(metadata.categories){
+ return this._renderList("Categories", metadata.categories);
+ }
+ }
+
+ _renderKeywords(metadata){
+ if(metadata.keywords){
+ return this._renderList("Keywords", metadata.keywords);
+ }
+ }
+
+ _renderExtensionDependencies(metadata){
+ if(metadata['extension-dependencies']){
+ return html`
+
+ ${this._renderExtensionDependenciesLines(metadata['extension-dependencies'])}
+
+ `;
+ }
+ }
+
+ _renderExtensionDependenciesLines(lines){
+ return html`
+ ${lines.map((line) =>
+ html`${line}`
+ )}
+ `;
+ }
+
+ _renderList(heading, list) {
+ return html`${heading}: ${this._renderListLines(list)}`;
+ }
+
+ _renderListLines(list) {
+ return html`
+
+ ${list.map((item) =>
+ html`- ${item}
`
+ )}
+
+ `;
+ }
+
+ _install(prop){
+ let extensionArtifactId = prop.artifact.groupId + ':' + prop.artifact.artifactId;
+ this.jsonRpc.addExtension({extensionArtifactId:extensionArtifactId}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+
+ const options = {
+ detail: {outcome: outcome, name: prop.name},
+ bubbles: true,
+ composed: true,
+ };
+ this.dispatchEvent(new CustomEvent('inprogress', options));
+
+ });
+ }
+}
+customElements.define('qwc-extension-add', QwcExtensionAdd);
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
index 5c76cb0bfe8bd..7f719cca05077 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
@@ -1,14 +1,19 @@
import { LitElement, html, css} from 'lit';
+import { observeState } from 'lit-element-state';
import '@vaadin/icon';
import '@vaadin/dialog';
import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
import '@qomponent/qui-badge';
+import { JsonRpc } from 'jsonrpc';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
/**
* This component represent one extension
* It's a card on the extension board
*/
-export class QwcExtension extends LitElement {
+export class QwcExtension extends observeState(LitElement) {
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.card {
@@ -95,13 +100,15 @@ export class QwcExtension extends LitElement {
builtWith: {type: String},
providesCapabilities: {},
extensionDependencies: {},
- favourite: {type: Boolean},
+ favourite: {type: Boolean},
+ installed: {type: Boolean},
};
constructor() {
super();
this._dialogOpened = false;
this.favourite = false;
+ this.installed = false;
}
render() {
@@ -260,9 +267,30 @@ export class QwcExtension extends LitElement {
${this._renderExtensionDependencies()} |
+ ${this._renderUninstallButton()}
`;
}
+ _renderUninstallButton(){
+ if(connectionState.current.isConnected && this.installed){
+ return html`
+
+ Remove this extension
+ `;
+ }
+ }
+
+ _uninstall(){
+ this._dialogOpened = false;
+ notifier.showInfoMessage(this.name + " removal in progress");
+ this.jsonRpc.removeExtension({extensionArtifactId:this.artifact}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+ if(!outcome){
+ notifier.showErrorMessage(name + " removal failed");
+ }
+ });
+ }
+
_renderGuideDetails() {
return this.guide
? html`${this.guide}`
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
index d555580dcaf02..14ef09471e3d0 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
@@ -5,7 +5,13 @@ import { devuiState } from 'devui-state';
import { observeState } from 'lit-element-state';
import 'qwc/qwc-extension.js';
import 'qwc/qwc-extension-link.js';
+import 'qwc/qwc-extension-add.js';
import { StorageController } from 'storage-controller';
+import '@vaadin/dialog';
+import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
+import { JsonRpc } from 'jsonrpc';
/**
* This component create cards of all the extensions
@@ -13,7 +19,7 @@ import { StorageController } from 'storage-controller';
export class QwcExtensions extends observeState(LitElement) {
routerController = new RouterController(this);
storageController = new StorageController(this);
-
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.grid {
display: flex;
@@ -49,22 +55,46 @@ export class QwcExtensions extends observeState(LitElement) {
qwc-extension-link {
cursor: grab;
}
+ .addExtensionButton {
+ position: absolute;
+ bottom: 40px;
+ right: 40px;
+ width: 3em;
+ height: 3em;
+ box-shadow: var(--lumo-shade) 5px 5px 15px 3px;
+ }
+ .addExtensionIcon {
+ width: 2em;
+ height: 2em;
+ }
`;
static properties = {
_favourites: {state: true},
+ _addDialogOpened: {state: true},
+ _installedExtensions: {state: true, type: Array},
}
constructor() {
super();
this._favourites = this._getStoredFavourites();
+ this._addDialogOpened = false;
+ this._installedExtensions = [];
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.jsonRpc.getInstalledNamespaces().then(jsonRpcResponse => {
+ this._installedExtensions = jsonRpcResponse.result;
+ });
}
render() {
return html`
${this._renderActives(devuiState.cards.active)}
${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
-
`;
+
+ ${this._renderAddDialog()}`;
}
_renderActives(extensions){
@@ -97,7 +127,6 @@ export class QwcExtensions extends observeState(LitElement) {
}
_renderActive(extension, fav){
-
return html`
@@ -243,5 +273,55 @@ export class QwcExtensions extends observeState(LitElement) {
`;
}
}
+
+ _renderAddDialog(){
+ return html`
+ {
+ this._addDialogOpened = event.detail.value;
+ }}"
+ ${dialogHeaderRenderer(
+ () => html`
+ (this._addDialogOpened = false)}">
+
+
+ `,
+ []
+ )}
+ ${dialogRenderer(
+ () => html``
+ )}
+ >
+ ${this._renderAddExtensionButton()}
+ `;
+ }
+
+ _renderAddExtensionButton(){
+ if(connectionState.current.isConnected){
+ return html`
+
+ `;
+ }
+ }
+
+ _installRequest(e){
+ this._addDialogOpened = false;
+ let name = e.detail.name;
+ if(e.detail.outcome){
+ notifier.showInfoMessage(name + " installation in progress");
+ }else{
+ notifier.showErrorMessage(name + " installation failed");
+ }
+ }
+
+ _openAddDialog() {
+ this._addDialogOpened = true;
+ }
+
}
customElements.define('qwc-extensions', QwcExtensions);
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java
index 4bca9c6c87856..f423d663e73b4 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java
@@ -2,25 +2,32 @@
import java.util.Objects;
-public class QuarkusCommandOutcome extends ValueMap {
+public class QuarkusCommandOutcome extends ValueMap> {
- public static QuarkusCommandOutcome success() {
- return new QuarkusCommandOutcome(true, null);
+ public static QuarkusCommandOutcome success() {
+ return new QuarkusCommandOutcome<>(true, null, null);
}
- public static QuarkusCommandOutcome failure(String message) {
+ public static QuarkusCommandOutcome success(T result) {
+ return new QuarkusCommandOutcome<>(true, null, result);
+ }
+
+ public static QuarkusCommandOutcome failure(String message) {
Objects.requireNonNull(message, "Message may not be null in case of a failure");
- return new QuarkusCommandOutcome(false, message);
+ return new QuarkusCommandOutcome(false, message, null);
}
private final boolean success;
private final String message;
- private QuarkusCommandOutcome(boolean success, String message) {
+ private final T result;
+
+ private QuarkusCommandOutcome(boolean success, String message, T result) {
this.success = success;
this.message = message;
+ this.result = result;
}
public boolean isSuccess() {
@@ -30,4 +37,8 @@ public boolean isSuccess() {
public String getMessage() {
return message;
}
+
+ public T getResult() {
+ return result;
+ }
}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
index e7a6a5f6ae4c3..a0bba897a88fa 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java
@@ -2,7 +2,9 @@
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
import io.quarkus.devtools.commands.ListCategories;
import io.quarkus.devtools.commands.data.QuarkusCommandException;
@@ -30,13 +32,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
final Collection categories = invocation.getExtensionsCatalog().getCategories();
- if (!batchMode) {
+ if (!batchMode && !format.equalsIgnoreCase("object")) {
log.info("Available Quarkus extension categories: ");
log.info("");
}
BiConsumer formatter;
switch (format.toLowerCase()) {
+ case "object":
+ formatter = null;
+ break;
case "full":
log.info(String.format(FULL_FORMAT, "Category", "CategoryId", "Description"));
formatter = this::fullFormatter;
@@ -50,10 +55,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
break;
}
- categories.stream()
- .sorted(Comparator.comparing(Category::getName))
- .forEach(c -> formatter.accept(log, c));
-
+ if (formatter != null) {
+ categories.stream()
+ .sorted(Comparator.comparing(Category::getName))
+ .forEach(c -> formatter.accept(log, c));
+ } else {
+ List sortedCategories = categories.stream()
+ .sorted(Comparator.comparing(Category::getName))
+ .collect(Collectors.toList());
+ return QuarkusCommandOutcome.success(sortedCategories);
+ }
return QuarkusCommandOutcome.success();
}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
index ca7154bc9f64c..0f05b84a02513 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java
@@ -61,11 +61,13 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
.getExtensions();
if (extensions.isEmpty()) {
- log.info("No extension found with pattern '%s'", search);
+ if (!format.equalsIgnoreCase("object")) {
+ log.info("No extension found with pattern '%s'", search);
+ }
return QuarkusCommandOutcome.success();
}
- if (!batchMode) {
+ if (!batchMode && !format.equalsIgnoreCase("object")) {
String extensionStatus = all ? "available" : "installable";
if (installedOnly)
extensionStatus = "installed";
@@ -75,6 +77,9 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
BiConsumer currentFormatter;
switch (format.toLowerCase()) {
+ case "object":
+ currentFormatter = null;
+ break;
case "id":
case "name":
currentFormatter = this::idFormatter;
@@ -110,11 +115,30 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
categoryFilter = e -> true;
}
- extensions.stream()
- .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
- .filter(categoryFilter)
- .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
- .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter));
+ if (currentFormatter != null) {
+ extensions.stream()
+ .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
+ .filter(categoryFilter)
+ .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
+ .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter));
+ } else {
+ List filteredExtensions = extensions.stream()
+ .filter(e -> !ExtensionProcessor.of(e).isUnlisted())
+ .filter(categoryFilter)
+ .filter(e -> {
+ ArtifactCoords installed = installedByKey.get(toKey(e));
+ if (installedOnly && installed == null) {
+ return false;
+ }
+ if (!installedOnly && !all && installed != null) {
+ return false;
+ }
+ return true;
+ })
+ .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId()))
+ .collect(Collectors.toList());
+ return QuarkusCommandOutcome.success(filteredExtensions);
+ }
return QuarkusCommandOutcome.success();
}
diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java
index bf50251b319e1..379cf8460636a 100644
--- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java
+++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java
@@ -2,6 +2,8 @@
import static io.quarkus.devtools.project.CodestartResourceLoadersBuilder.getCodestartResourceLoaders;
+import java.io.OutputStream;
+import java.io.PrintStream;
import java.nio.file.Path;
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
@@ -16,7 +18,7 @@
import io.quarkus.registry.config.RegistriesConfig;
public class QuarkusProjectHelper {
-
+ private static QuarkusProject cachedProject;
private static RegistriesConfig toolsConfig;
private static MessageWriter log;
private static MavenArtifactResolver artifactResolver;
@@ -44,6 +46,33 @@ public static BuildTool detectExistingBuildTool(Path projectDirPath) {
return BuildTool.fromProject(projectDirPath);
}
+ public static QuarkusProject getCachedProject(Path projectDir) {
+ if (cachedProject == null) {
+ PrintStream nullPrintStream = new PrintStream(OutputStream.nullOutputStream());
+ log = MessageWriter.info(nullPrintStream);
+ BuildTool buildTool = detectExistingBuildTool(projectDir);
+ if (buildTool == null) {
+ buildTool = BuildTool.MAVEN;
+ }
+ if (BuildTool.MAVEN.equals(buildTool)) {
+ try {
+ return MavenProjectBuildFile.getProject(projectDir, log, null);
+ } catch (RegistryResolutionException e) {
+ throw new RuntimeException("Failed to initialize the Quarkus Maven extension manager", e);
+ }
+ }
+ final ExtensionCatalog catalog;
+ try {
+ catalog = resolveExtensionCatalog();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to resolve the Quarkus extension catalog", e);
+ }
+ cachedProject = getProject(projectDir, catalog, buildTool, JavaVersion.NA, log);
+ }
+
+ return cachedProject;
+ }
+
public static QuarkusProject getProject(Path projectDir) {
BuildTool buildTool = detectExistingBuildTool(projectDir);
if (buildTool == null) {
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/CompileOnlyDependencyFlagsTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/CompileOnlyDependencyFlagsTest.java
index b7e8ed45f80da..d6e61194b96b8 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/CompileOnlyDependencyFlagsTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/CompileOnlyDependencyFlagsTest.java
@@ -86,7 +86,8 @@ public void compileOnlyFlags() throws Exception {
DependencyFlags.COMPILE_ONLY);
assertOnlyFlagsSet(bootstrapResolver, compileOnly.get(bootstrapResolver),
DependencyFlags.COMPILE_ONLY,
- DependencyFlags.CLASSLOADER_PARENT_FIRST);
+ DependencyFlags.CLASSLOADER_PARENT_FIRST,
+ DependencyFlags.DEPLOYMENT_CP);
compileOnly = compileOnlyDeps.get(LaunchMode.NORMAL.name());
assertEqual(compileOnly, expectedCompileOnly);
@@ -99,7 +100,8 @@ public void compileOnlyFlags() throws Exception {
DependencyFlags.COMPILE_ONLY);
assertOnlyFlagsSet(bootstrapResolver, compileOnly.get(bootstrapResolver),
DependencyFlags.COMPILE_ONLY,
- DependencyFlags.CLASSLOADER_PARENT_FIRST);
+ DependencyFlags.CLASSLOADER_PARENT_FIRST,
+ DependencyFlags.DEPLOYMENT_CP);
}
private static void assertOnlyFlagsSet(String coords, int flags, int... expectedFlags) {