Skip to content

Commit

Permalink
Introduce transitive extensions in api and UI (#1464)
Browse files Browse the repository at this point in the history
  • Loading branch information
ia3andy authored Aug 16, 2024
1 parent dba0775 commit 805057e
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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())
Expand Down
32 changes: 8 additions & 24 deletions base/src/main/java/io/quarkus/code/model/CodeQuarkusExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -16,6 +15,7 @@ public record CodeQuarkusExtension(
String description,
String shortName,
String category,
List<String> transitiveExtensions,
List<String> tags,
Set<String> keywords,
@Deprecated boolean providesExampleCode,
Expand All @@ -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;
Expand All @@ -66,6 +43,7 @@ public static class Builder {
private String shortName = "";
private String category;
private List<String> tags;
private List<String> transitiveExtensions = List.of();
private Set<String> keywords;
private boolean providesExampleCode;
private boolean providesCode;
Expand Down Expand Up @@ -112,6 +90,11 @@ public Builder category(String category) {
return this;
}

public Builder transitiveExtensions(List<String> transitiveExtensions) {
this.transitiveExtensions = transitiveExtensions;
return this;
}

public Builder tags(List<String> tags) {
this.tags = tags;
return this;
Expand Down Expand Up @@ -161,6 +144,7 @@ public CodeQuarkusExtension build() {
description,
shortName,
category,
transitiveExtensions,
tags,
keywords,
providesExampleCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Config, Platform, Tag } from './model';
import _ from "lodash";

let platformCache: Map<string, Platform> = new Map<string, Platform>();
let config: Config | undefined;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions base/src/main/resources/web/lib/components/api/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface Extension {
version: string;
name: string;
keywords: string[];
transitiveExtensions: string[];
tags: string[];
description?: string;
shortName?: string;
Expand All @@ -57,6 +58,7 @@ export interface PlatformMappedExtensions {

export interface Platform {
extensions: Extension[];
extensionById: {[index: string]: Extension};
streams: Stream[];
presets: Preset[];
tagsDef: Tag[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
pointer-events: none;
}

&.transitive {
color: var(--readOnlyTextColor) !important;
}

.extension-selector {
color: var(--extensionsPickerCheckColor);
}
Expand All @@ -27,6 +31,14 @@
color: var(--extensionsPickerCheckSelectedHoverColor);
}

&.transitive .extension-selector {
color: var(--readOnlyTextColor);
}

&.hover.transitive .extension-selector {
color: var(--readOnlyTextColor);
}

div:focus {
outline: none;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -28,6 +29,9 @@ export function ExtensionRow(props: ExtensionRowProps) {
}

const onClick = () => {
if (props.transitive) {
return;
}
props.onClick(props.id);
setHover(false);
};
Expand All @@ -45,13 +49,15 @@ 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}`);
return (
<div {...activationEvents} className={classNames('extension-row', {
'keyboard-actived': props.keyboardActived,
hover,
transitive,
selected,
'by-default': props.default
})} ref={ref} aria-label={props.id} >
Expand All @@ -72,7 +78,7 @@ export function ExtensionRow(props: ExtensionRowProps) {
{props.tags && props.tags.map((s, i) => <ExtensionTags key={i} tagsDef={props.tagsDef} name={s} hover={hover}/>)}
</div>

{props.layout === 'cart' && (
{props.layout === 'cart' && !props.transitive && (
<div
className="extension-remove"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ExtensionEntry {
name: string;
version: string;
keywords: string[];
transitiveExtensions: string[];
tags: string[];
description?: string;
shortName?: string;
Expand Down Expand Up @@ -173,7 +174,7 @@ export const ExtensionsPicker = (props: ExtensionsPickerProps) => {
extensions<FaAngleRight/></Button></div>
{props.project.extensions.length === 0 ?
<PresetsPanel platform={props.platform} select={addById}/> :
<SelectedExtensions extensions={extensions} tagsDef={props.tagsDef} remove={removeById} layout="picker"/>
<SelectedExtensions platform={props.platform} extensions={extensions} tagsDef={props.tagsDef} remove={removeById} layout="picker"/>
}
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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`
Expand All @@ -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);
Expand Down Expand Up @@ -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<boolean>(false);
const clear = () => {
props.remove('*', 'Selection clear');
};

function flipTransitive() {
setShowTransitive(!showTransitive);
}

const layout = props.layout || 'cart';
let transitiveExtensions = [...new Set<string>(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 (
<SelectedExtensionsDiv className={classNames('selected-extensions', layout)}>
<h4>
Selected Extensions
{props.extensions.length > 0 && <button className="btn btn-light btn-clear" onClick={clear} aria-label="Clear extension selection">
<FaTrashAlt/>Clear selection
</button>}
{props.extensions.length > 0 &&
<button className="btn btn-light btn-clear" onClick={clear} aria-label="Clear extension selection">
<FaTrashAlt/>Clear selection
</button>}
</h4>
{props.extensions.length === 0 && (
<Alert variant="warning">
Expand All @@ -113,8 +143,26 @@ export const SelectedExtensions = (props: {layout?: 'cart' | 'picker'; extension
/>
))
}
</div>

</div>
<h5 onClick={flipTransitive}>Transitive extensions ({transitiveExtensions.length}) {showTransitive ?
<FaAngleUp/> : <FaAngleDown/>}</h5>
{showTransitive &&
<div className="extension-list-wrapper transitive">
{
transitiveExtensions.map((ex, i) => (
<ExtensionRow
{...ex}
key={i}
selected={true}
transitive={true}
layout={layout}
tagsDef={props.tagsDef}
/>
))
}
</div>
}
</>
)}
</SelectedExtensionsDiv>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExtensionsCartValue> {
platform: Platform;
tagsDef: TagEntry[];
}

Expand Down Expand Up @@ -53,7 +55,7 @@ export function ExtensionsCart(props: ExtensionsCartProps) {
</DropdownToggle>

<Dropdown.Menu onMouseEnter={onMouseEnterFn} align="left">
<SelectedExtensions extensions={props.value.extensions} remove={onRemove} tagsDef={props.tagsDef}/>
<SelectedExtensions platform={props.platform} extensions={props.value.extensions} remove={onRemove} tagsDef={props.tagsDef}/>
</Dropdown.Menu>
</Dropdown>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function CodeQuarkusForm(props: CodeQuarkusFormProps) {
<InfoPicker currentStream={currentStream} value={props.project.metadata} onChange={setMetadata} />
</div>
<div className="generate-project">
<ExtensionsCart value={{ extensions: props.selectedExtensions }} onChange={setExtensions} tagsDef={props.platform.tagsDef}/>
<ExtensionsCart platform={props.platform} value={{ extensions: props.selectedExtensions }} onChange={setExtensions} tagsDef={props.platform.tagsDef}/>
<GenerateButton
api={props.api}
project={props.project}
Expand Down

0 comments on commit 805057e

Please sign in to comment.