From 805057e0a608e30fe03695c8c0ce4e13535fc3aa Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 16 Aug 2024 13:07:58 +0200 Subject: [PATCH] Introduce transitive extensions in api and UI (#1464) --- .../code/misc/QuarkusExtensionUtils.java | 2 +- .../code/model/CodeQuarkusExtension.java | 32 +++------- .../quarkus/code/service/PlatformService.java | 6 +- .../lib/components/api/code-quarkus-api.ts | 2 + .../resources/web/lib/components/api/model.ts | 2 + .../extensions-picker/extension-row.scss | 12 ++++ .../extensions-picker/extension-row.tsx | 10 ++- .../extensions-picker/extensions-picker.tsx | 3 +- .../extensions-picker/presets-panel.tsx | 6 +- .../extensions-picker/selected-extensions.tsx | 62 ++++++++++++++++--- .../generate-project/extensions-cart.tsx | 4 +- .../quarkus-project-edition-form.tsx | 2 +- 12 files changed, 101 insertions(+), 42 deletions(-) diff --git a/base/src/main/java/io/quarkus/code/misc/QuarkusExtensionUtils.java b/base/src/main/java/io/quarkus/code/misc/QuarkusExtensionUtils.java index 09846ed8d..bd7450bd9 100644 --- a/base/src/main/java/io/quarkus/code/misc/QuarkusExtensionUtils.java +++ b/base/src/main/java/io/quarkus/code/misc/QuarkusExtensionUtils.java @@ -1,6 +1,5 @@ package io.quarkus.code.misc; -import com.google.common.collect.Lists; import io.quarkus.code.model.CodeQuarkusExtension; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.platform.catalog.processor.CatalogProcessor; @@ -60,6 +59,7 @@ public static CodeQuarkusExtension toCodeQuarkusExtension( .category(cat.getName()) .tags(getTags(extensionProcessor)) .keywords(extensionProcessor.getExtendedKeywords()) + .transitiveExtensions(ExtensionProcessor.getMetadataValue(ext, "extension-dependencies").asStringList()) .order(order.getAndIncrement()) .providesCode(extensionProcessor.providesCode()) .providesExampleCode(extensionProcessor.providesCode()) diff --git a/base/src/main/java/io/quarkus/code/model/CodeQuarkusExtension.java b/base/src/main/java/io/quarkus/code/model/CodeQuarkusExtension.java index c95114f1c..09f07178c 100644 --- a/base/src/main/java/io/quarkus/code/model/CodeQuarkusExtension.java +++ b/base/src/main/java/io/quarkus/code/model/CodeQuarkusExtension.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.util.List; -import java.util.Objects; import java.util.Set; @JsonInclude(Include.NON_NULL) @@ -16,6 +15,7 @@ public record CodeQuarkusExtension( String description, String shortName, String category, + List transitiveExtensions, List tags, Set keywords, @Deprecated boolean providesExampleCode, @@ -34,29 +34,6 @@ public static Builder builder() { return new Builder(); } - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - CodeQuarkusExtension that = (CodeQuarkusExtension) o; - return providesExampleCode == that.providesExampleCode && providesCode == that.providesCode && order == that.order - && platform == that.platform && Objects.equals(id, that.id) && Objects.equals(shortId, - that.shortId) - && Objects.equals(version, that.version) && Objects.equals(name, that.name) - && Objects.equals(description, that.description) && Objects.equals(shortName, that.shortName) - && Objects.equals(category, that.category) && Objects.equals(tags, that.tags) - && Objects.equals(keywords, that.keywords) && Objects.equals(guide, that.guide) - && Objects.equals(bom, that.bom); - } - - @Override - public int hashCode() { - return Objects.hash(id, shortId, version, name, description, shortName, category, tags, keywords, providesExampleCode, - providesCode, guide, order, platform, bom); - } - public static class Builder { private String id; private String shortId; @@ -66,6 +43,7 @@ public static class Builder { private String shortName = ""; private String category; private List tags; + private List transitiveExtensions = List.of(); private Set keywords; private boolean providesExampleCode; private boolean providesCode; @@ -112,6 +90,11 @@ public Builder category(String category) { return this; } + public Builder transitiveExtensions(List transitiveExtensions) { + this.transitiveExtensions = transitiveExtensions; + return this; + } + public Builder tags(List tags) { this.tags = tags; return this; @@ -161,6 +144,7 @@ public CodeQuarkusExtension build() { description, shortName, category, + transitiveExtensions, tags, keywords, providesExampleCode, diff --git a/base/src/main/java/io/quarkus/code/service/PlatformService.java b/base/src/main/java/io/quarkus/code/service/PlatformService.java index 859071f8e..26f108c38 100644 --- a/base/src/main/java/io/quarkus/code/service/PlatformService.java +++ b/base/src/main/java/io/quarkus/code/service/PlatformService.java @@ -77,9 +77,11 @@ public class PlatformService { "https://raw.githubusercontent.com/quarkusio/code.quarkus.io/main/base/assets/icons/presets/webapp-npm.svg", List.of("io.quarkus:quarkus-rest", "io.quarkus:quarkus-rest-jackson", "io.quarkiverse.quinoa:quarkus-quinoa")), - new Preset("webapp-qute", "Web app with ServerSide Rendering", "https://raw.githubusercontent.com/quarkusio/code.quarkus.io/main/base/assets/icons/presets/webapp-qute.svg", + new Preset("webapp-qute", "Web app with ServerSide Rendering", + "https://raw.githubusercontent.com/quarkusio/code.quarkus.io/main/base/assets/icons/presets/webapp-qute.svg", List.of("io.quarkiverse.qute.web:quarkus-qute-web", "io.quarkiverse.web-bundler:quarkus-web-bundler")), - new Preset("ai-infused", "AI Infused service", "https://raw.githubusercontent.com/quarkusio/code.quarkus.io/main/base/assets/icons/presets/ai-infused.svg", + new Preset("ai-infused", "AI Infused service", + "https://raw.githubusercontent.com/quarkusio/code.quarkus.io/main/base/assets/icons/presets/ai-infused.svg", List.of("io.quarkiverse.langchain4j:quarkus-langchain4j-openai", "io.quarkiverse.langchain4j:quarkus-langchain4j-easy-rag"))); diff --git a/base/src/main/resources/web/lib/components/api/code-quarkus-api.ts b/base/src/main/resources/web/lib/components/api/code-quarkus-api.ts index c55e18dea..44c8a6891 100644 --- a/base/src/main/resources/web/lib/components/api/code-quarkus-api.ts +++ b/base/src/main/resources/web/lib/components/api/code-quarkus-api.ts @@ -1,4 +1,5 @@ import { Config, Platform, Tag } from './model'; +import _ from "lodash"; let platformCache: Map = new Map(); let config: Config | undefined; @@ -97,6 +98,7 @@ export async function fetchPlatform(api: Api, streamKey?: string, platformOnly: let platform = { extensions: json[0], + extensionById: _.keyBy(json[0], ({id}) => id), streams: json[1], presets: json[2], tagsDef: api.tagsDef || DEFAULT_TAGS diff --git a/base/src/main/resources/web/lib/components/api/model.ts b/base/src/main/resources/web/lib/components/api/model.ts index e92112f22..44663a6e4 100644 --- a/base/src/main/resources/web/lib/components/api/model.ts +++ b/base/src/main/resources/web/lib/components/api/model.ts @@ -31,6 +31,7 @@ export interface Extension { version: string; name: string; keywords: string[]; + transitiveExtensions: string[]; tags: string[]; description?: string; shortName?: string; @@ -57,6 +58,7 @@ export interface PlatformMappedExtensions { export interface Platform { extensions: Extension[]; + extensionById: {[index: string]: Extension}; streams: Stream[]; presets: Preset[]; tagsDef: Tag[]; diff --git a/base/src/main/resources/web/lib/components/extensions-picker/extension-row.scss b/base/src/main/resources/web/lib/components/extensions-picker/extension-row.scss index c5fdaee96..cff913a14 100644 --- a/base/src/main/resources/web/lib/components/extensions-picker/extension-row.scss +++ b/base/src/main/resources/web/lib/components/extensions-picker/extension-row.scss @@ -15,6 +15,10 @@ pointer-events: none; } + &.transitive { + color: var(--readOnlyTextColor) !important; + } + .extension-selector { color: var(--extensionsPickerCheckColor); } @@ -27,6 +31,14 @@ color: var(--extensionsPickerCheckSelectedHoverColor); } + &.transitive .extension-selector { + color: var(--readOnlyTextColor); + } + + &.hover.transitive .extension-selector { + color: var(--readOnlyTextColor); + } + div:focus { outline: none; } diff --git a/base/src/main/resources/web/lib/components/extensions-picker/extension-row.tsx b/base/src/main/resources/web/lib/components/extensions-picker/extension-row.tsx index ed1a65793..f60f300f6 100644 --- a/base/src/main/resources/web/lib/components/extensions-picker/extension-row.tsx +++ b/base/src/main/resources/web/lib/components/extensions-picker/extension-row.tsx @@ -13,8 +13,9 @@ export interface ExtensionRowProps extends ExtensionEntry { layout?: 'picker' | 'cart'; buildTool?: string; tagsDef: TagEntry[]; + transitive?: boolean; - onClick(id: string): void; + onClick?(id: string): void; } export function ExtensionRow(props: ExtensionRowProps) { @@ -28,6 +29,9 @@ export function ExtensionRow(props: ExtensionRowProps) { } const onClick = () => { + if (props.transitive) { + return; + } props.onClick(props.id); setHover(false); }; @@ -45,6 +49,7 @@ export function ExtensionRow(props: ExtensionRowProps) { }, [ props.keyboardActived ]) const description = props.description || '...'; + const transitive = props.transitive; const selected = props.selected || props.default; const ga = props.id.split(':'); const id = ga[1] + (props.platform ? '' : `:${props.version}`); @@ -52,6 +57,7 @@ export function ExtensionRow(props: ExtensionRowProps) {
@@ -72,7 +78,7 @@ export function ExtensionRow(props: ExtensionRowProps) { {props.tags && props.tags.map((s, i) => )}
- {props.layout === 'cart' && ( + {props.layout === 'cart' && !props.transitive && (
diff --git a/base/src/main/resources/web/lib/components/extensions-picker/extensions-picker.tsx b/base/src/main/resources/web/lib/components/extensions-picker/extensions-picker.tsx index 27386e924..990ecc340 100644 --- a/base/src/main/resources/web/lib/components/extensions-picker/extensions-picker.tsx +++ b/base/src/main/resources/web/lib/components/extensions-picker/extensions-picker.tsx @@ -19,6 +19,7 @@ export interface ExtensionEntry { name: string; version: string; keywords: string[]; + transitiveExtensions: string[]; tags: string[]; description?: string; shortName?: string; @@ -173,7 +174,7 @@ export const ExtensionsPicker = (props: ExtensionsPickerProps) => { extensions
{props.project.extensions.length === 0 ? : - + } diff --git a/base/src/main/resources/web/lib/components/extensions-picker/presets-panel.tsx b/base/src/main/resources/web/lib/components/extensions-picker/presets-panel.tsx index 59d73b613..8920c86f1 100644 --- a/base/src/main/resources/web/lib/components/extensions-picker/presets-panel.tsx +++ b/base/src/main/resources/web/lib/components/extensions-picker/presets-panel.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import {TagEntry} from "./extensions-picker"; import {Platform, Preset} from "../api/model"; -import _ from "lodash"; + import {useAnalytics} from "../../core/analytics"; interface PresetsProps { @@ -66,9 +66,9 @@ export const PresetCard = (props: { preset: Preset, tagsDef: TagEntry[], onClick export const PresetsPanel = (props: PresetsProps) => { let analytics = useAnalytics(); const context = {element: 'preset-picker'}; - const byId = _.keyBy(props.platform.extensions, ({id}) => id); + const extensionById = props.platform.extensionById; const presets = props.platform.presets.map(p => ({ - ...p, resolvedExtensions: p.extensions.filter(e => byId[e]).map(e => byId[e]) + ...p, resolvedExtensions: p.extensions.filter(e => extensionById[e]).map(e => extensionById[e]) } as Preset)) const selectPreset = (preset: Preset) => { diff --git a/base/src/main/resources/web/lib/components/extensions-picker/selected-extensions.tsx b/base/src/main/resources/web/lib/components/extensions-picker/selected-extensions.tsx index b76fd8aa4..8d6334771 100644 --- a/base/src/main/resources/web/lib/components/extensions-picker/selected-extensions.tsx +++ b/base/src/main/resources/web/lib/components/extensions-picker/selected-extensions.tsx @@ -1,10 +1,11 @@ -import React from 'react'; +import React, {useState} from 'react'; import {ExtensionRow} from "./extension-row"; import styled from 'styled-components'; -import {FaExclamation, FaTrashAlt} from 'react-icons/fa'; +import {FaExclamation, FaTrashAlt, FaAngleDown, FaAngleUp} from 'react-icons/fa'; import {Alert} from 'react-bootstrap'; import {ExtensionEntry, TagEntry} from "./extensions-picker"; import classNames from 'classnames'; +import {Platform} from "../api/model"; const SelectedExtensionsDiv = styled.div` @@ -20,6 +21,17 @@ const SelectedExtensionsDiv = styled.div` } + h5 { + margin-top: 20px; + cursor: pointer; + user-select: none; + + svg { + width: 20px; + vertical-align: middle; + } + } + &.picker { h4 { border-bottom: 1px solid var(--extensionsPickerCategoryUnderlineColor); @@ -79,18 +91,36 @@ const SelectedExtensionsDiv = styled.div` ` -export const SelectedExtensions = (props: {layout?: 'cart' | 'picker'; extensions: ExtensionEntry[]; tagsDef: TagEntry[]; remove: (id: string, type: string) => void }) => { +export const SelectedExtensions = (props: { + layout?: 'cart' | 'picker', + extensions: ExtensionEntry[], + tagsDef: TagEntry[], + remove: (id: string, type: string) => void, + platform: Platform +}) => { + const [showTransitive, setShowTransitive] = useState(false); const clear = () => { props.remove('*', 'Selection clear'); }; + + function flipTransitive() { + setShowTransitive(!showTransitive); + } + const layout = props.layout || 'cart'; + let transitiveExtensions = [...new Set(props.extensions + .flatMap((ex) => ex.transitiveExtensions) + .filter(id => props.platform.extensionById[id]))] + .map(id => props.platform.extensionById[id]) + .filter(ex => props.extensions.indexOf(ex) < 0); return (

Selected Extensions - {props.extensions.length > 0 && } + {props.extensions.length > 0 && + }

{props.extensions.length === 0 && ( @@ -113,8 +143,26 @@ export const SelectedExtensions = (props: {layout?: 'cart' | 'picker'; extension /> )) } - + +
Transitive extensions ({transitiveExtensions.length}) {showTransitive ? + : }
+ {showTransitive && +
+ { + transitiveExtensions.map((ex, i) => ( + + )) + } +
+ } )}
diff --git a/base/src/main/resources/web/lib/components/generate-project/extensions-cart.tsx b/base/src/main/resources/web/lib/components/generate-project/extensions-cart.tsx index 4f1d6d99b..2d6e0195b 100644 --- a/base/src/main/resources/web/lib/components/generate-project/extensions-cart.tsx +++ b/base/src/main/resources/web/lib/components/generate-project/extensions-cart.tsx @@ -9,12 +9,14 @@ import {InputProps} from '../../core/types'; import _ from 'lodash'; import classNames from 'classnames'; import {SelectedExtensions} from "../extensions-picker/selected-extensions"; +import {Platform} from "../api/model"; export interface ExtensionsCartValue { extensions: ExtensionEntry[]; } export interface ExtensionsCartProps extends InputProps { + platform: Platform; tagsDef: TagEntry[]; } @@ -53,7 +55,7 @@ export function ExtensionsCart(props: ExtensionsCartProps) { - + ); diff --git a/base/src/main/resources/web/lib/components/quarkus-project/quarkus-project-edition-form.tsx b/base/src/main/resources/web/lib/components/quarkus-project/quarkus-project-edition-form.tsx index 32a7cc020..3860d1ce3 100644 --- a/base/src/main/resources/web/lib/components/quarkus-project/quarkus-project-edition-form.tsx +++ b/base/src/main/resources/web/lib/components/quarkus-project/quarkus-project-edition-form.tsx @@ -77,7 +77,7 @@ export function CodeQuarkusForm(props: CodeQuarkusFormProps) {
- +