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

Issue 215: Use API defaults for a simpler experience #235

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@
],
"scope": "window",
"description": "Controls if a new sub-folder should be created for the newly generated project."
},
"spring.initializr.enableSmartDefaults": {
"default": false,
"type": "boolean",
"scope": "window",
"description": "Use default values provided by Spring Initializr API for Spring Boot version, language, Java version and packaging."
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/handler/GenerateProjectHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ export class GenerateProjectHandler extends BaseHandler {
constructor(projectType: "maven-project" | "gradle-project", defaults?: IDefaultProjectData) {
super();
this.projectType = projectType;

const settings = vscode.workspace.getConfiguration("spring.initializr");

this.metadata = {
pickSteps: [],
defaults: defaults || {},
parentFolder: vscode.workspace.getConfiguration("spring.initializr").get<ParentFolder>("parentFolder")
parentFolder: settings.get<ParentFolder>("parentFolder"),
enableSmartDefaults: settings.get("enableSmartDefaults")
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/handler/HandlerInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface IProjectMetadata {
pickSteps: IStep[];
defaults: IDefaultProjectData;
parentFolder?: ParentFolder;
enableSmartDefaults?: boolean;
}

export interface IDefaultProjectData {
Expand All @@ -37,6 +38,7 @@ export interface IDefaultProjectData {
export interface IHandlerItem<T extends Identifiable> extends QuickPickItem {
label: string;
value?: T;
default?: boolean;
}

export interface IPickMetadata<T extends Identifiable> {
Expand Down
12 changes: 10 additions & 2 deletions src/handler/SpecifyBootVersionStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { instrumentOperationStep, sendInfo } from "vscode-extension-telemetry-wrapper";
import { serviceManager } from "../model";
import { BootVersion, MatadataType } from "../model/Metadata";
import { BootVersion, MetadataType } from "../model/Metadata";
import { IPickMetadata, IProjectMetadata, IStep } from "./HandlerInterfaces";
import { SpecifyLanguageStep } from "./SpecifyLanguageStep";
import { createPickBox } from "./utils";
Expand All @@ -29,13 +29,21 @@ export class SpecifyBootVersionStep implements IStep {
}

private async specifyBootVersion(projectMetadata: IProjectMetadata): Promise<boolean> {
const items = await serviceManager.getItems(projectMetadata.serviceUrl, MetadataType.BOOTVERSION);

if (projectMetadata.enableSmartDefaults === true) {
projectMetadata.bootVersion = items.find(x => x.default === true)?.value?.id;
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should guard the case of projectMetadata.bootVersion is null, and fall back to the prompt mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed that for every API default. Thanks!

return true;
}

const pickMetaData: IPickMetadata<BootVersion> = {
metadata: projectMetadata,
title: "Spring Initializr: Specify Spring Boot version",
pickStep: SpecifyBootVersionStep.getInstance(),
placeholder: "Specify Spring Boot version.",
items: serviceManager.getItems(projectMetadata.serviceUrl, MatadataType.BOOTVERSION),
items: items,
};

return await createPickBox(pickMetaData);
}
}
14 changes: 12 additions & 2 deletions src/handler/SpecifyJavaVersionStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { workspace } from "vscode";
import { instrumentOperationStep } from "vscode-extension-telemetry-wrapper";
import { serviceManager } from "../model";
import { JavaVersion, MatadataType } from "../model/Metadata";
import { JavaVersion, MetadataType } from "../model/Metadata";
import { IPickMetadata, IProjectMetadata, IStep } from "./HandlerInterfaces";
import { SpecifyDependenciesStep } from "./SpecifyDependenciesStep";
import { createPickBox } from "./utils";
Expand All @@ -30,17 +30,27 @@ export class SpecifyJavaVersionStep implements IStep {

private async specifyJavaVersion(projectMetadata: IProjectMetadata): Promise<boolean> {
const javaVersion: string = projectMetadata.defaults.javaVersion || workspace.getConfiguration("spring.initializr").get<string>("defaultJavaVersion");

if (javaVersion) {
projectMetadata.javaVersion = javaVersion;
return true;
}

const items = await serviceManager.getItems(projectMetadata.serviceUrl, MetadataType.JAVAVERSION);

if (projectMetadata.enableSmartDefaults === true) {
projectMetadata.javaVersion = items.find(x => x.default === true)?.value?.id;
return true;
}

const pickMetaData: IPickMetadata<JavaVersion> = {
metadata: projectMetadata,
title: "Spring Initializr: Specify Java version",
pickStep: SpecifyJavaVersionStep.getInstance(),
placeholder: "Specify Java version.",
items: serviceManager.getItems(projectMetadata.serviceUrl, MatadataType.JAVAVERSION),
items: items
};

return await createPickBox(pickMetaData);
}
}
14 changes: 12 additions & 2 deletions src/handler/SpecifyLanguageStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { workspace } from "vscode";
import { instrumentOperationStep } from "vscode-extension-telemetry-wrapper";
import { serviceManager } from "../model";
import { Language, MatadataType } from "../model/Metadata";
import { Language, MetadataType } from "../model/Metadata";
import { IPickMetadata, IProjectMetadata, IStep } from "./HandlerInterfaces";
import { SpecifyGroupIdStep } from "./SpecifyGroupIdStep";
import { createPickBox } from "./utils";
Expand All @@ -30,17 +30,27 @@ export class SpecifyLanguageStep implements IStep {

private async specifyLanguage(projectMetadata: IProjectMetadata): Promise<boolean> {
const language: string = projectMetadata.defaults.language || workspace.getConfiguration("spring.initializr").get<string>("defaultLanguage");

if (language) {
projectMetadata.language = language && language.toLowerCase();
return true;
}

const items = await serviceManager.getItems(projectMetadata.serviceUrl, MetadataType.LANGUAGE);

if (projectMetadata.enableSmartDefaults === true) {
projectMetadata.language = items.find(x => x.default === true)?.label.toLowerCase();
return true;
}

const pickMetaData: IPickMetadata<Language> = {
metadata: projectMetadata,
title: "Spring Initializr: Specify project language",
pickStep: SpecifyLanguageStep.getInstance(),
placeholder: "Specify project language.",
items: serviceManager.getItems(projectMetadata.serviceUrl, MatadataType.LANGUAGE),
items: items
};

return await createPickBox(pickMetaData);
}
}
14 changes: 12 additions & 2 deletions src/handler/SpecifyPackagingStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { workspace } from "vscode";
import { instrumentOperationStep } from "vscode-extension-telemetry-wrapper";
import { serviceManager } from "../model";
import { MatadataType, Packaging } from "../model/Metadata";
import { MetadataType, Packaging } from "../model/Metadata";
import { IPickMetadata, IProjectMetadata, IStep } from "./HandlerInterfaces";
import { SpecifyJavaVersionStep } from "./SpecifyJavaVersionStep";
import { createPickBox } from "./utils";
Expand All @@ -30,17 +30,27 @@ export class SpecifyPackagingStep implements IStep {

private async specifyPackaging(projectMetadata: IProjectMetadata): Promise<boolean> {
const packaging: string = projectMetadata.defaults.packaging || workspace.getConfiguration("spring.initializr").get<string>("defaultPackaging");

if (packaging) {
projectMetadata.packaging = packaging && packaging.toLowerCase();
return true;
}

const items = await serviceManager.getItems(projectMetadata.serviceUrl, MetadataType.PACKAGING);

if (projectMetadata.enableSmartDefaults === true) {
projectMetadata.packaging = items.find(x => x.default === true)?.label?.toLowerCase();
return true;
}

const pickMetaData: IPickMetadata<Packaging> = {
metadata: projectMetadata,
title: "Spring Initializr: Specify packaging type",
pickStep: SpecifyPackagingStep.getInstance(),
placeholder: "Specify packaging type.",
items: serviceManager.getItems(projectMetadata.serviceUrl, MatadataType.PACKAGING),
items: items
};

return await createPickBox(pickMetaData);
}
}
2 changes: 1 addition & 1 deletion src/model/Metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface Metadata {
type: Category<ProjectType>;
}

export enum MatadataType {
export enum MetadataType {
BOOTVERSION,
JAVAVERSION,
LANGUAGE,
Expand Down
39 changes: 26 additions & 13 deletions src/model/ServiceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IDependency, IStarters } from ".";
import { IHandlerItem } from "../handler/HandlerInterfaces";
import { downloadFile } from "../Utils";
import { matchRange } from "../Utils/VersionHelper";
import { DependencyGroup, Identifiable, MatadataType, Metadata } from "./Metadata";
import { DependencyGroup, Identifiable, MetadataType, Metadata } from "./Metadata";

/**
* Prefer v2.2 and fallback to v2.1
Expand All @@ -17,44 +17,57 @@ const METADATA_HEADERS = { Accept: "application/vnd.initializr.v2.2+json,applica
class ServiceManager {
private metadataMap: Map<string, Metadata> = new Map();

public async getItems<T extends Identifiable>(serviceUrl: string, type: MatadataType): Promise<Array<IHandlerItem<T>>> {
public async getItems<T extends Identifiable>(serviceUrl: string, type: MetadataType): Promise<Array<IHandlerItem<T>>> {
const metadata = await this.ensureMetadata(serviceUrl);
if (!metadata) {
throw new Error("Failed to fetch metadata.");
}
let defaultLabel: string;
let values: any[];
switch (type) {
case MatadataType.BOOTVERSION:
case MetadataType.BOOTVERSION:
defaultLabel = metadata.bootVersion.default;
values = metadata.bootVersion.values;
break;
case MatadataType.JAVAVERSION:
case MetadataType.JAVAVERSION:
defaultLabel = metadata.javaVersion.default;
values = metadata.javaVersion.values;
break;
case MatadataType.LANGUAGE:
case MetadataType.LANGUAGE:
defaultLabel = metadata.language.default;
values = metadata.language.values;
break;
case MatadataType.PACKAGING:
case MetadataType.PACKAGING:
defaultLabel = metadata.packaging.default;
values = metadata.packaging.values;
break;
default:
throw new Error("Invalid metadata type.");
}

const sortedValues = values.filter(x => x.id === defaultLabel).concat(values.filter(x => x.id !== defaultLabel));
const defaultValues = values.filter(x => x.id === defaultLabel);
const nonDefaultValues = values.filter(x => x.id !== defaultLabel);

let mappedDefault: Array<IHandlerItem<T>> = this.mapValues(defaultValues, type, true);
let mappedNonDefault: Array<IHandlerItem<T>> = this.mapValues(nonDefaultValues, type, false);

return mappedDefault.concat(mappedNonDefault);
}

private mapValues<T extends Identifiable>(
items: any[],
type: MetadataType,
isDefault: boolean
): Array<IHandlerItem<T>> {
switch (type) {
case MatadataType.BOOTVERSION:
return sortedValues.map(v => ({ value: v, label: v.name }));
case MatadataType.JAVAVERSION:
return sortedValues.map(v => ({ value: v, label: v.name }));
case MetadataType.BOOTVERSION:
return items.map(v => ({ value: v, label: v.name, default: isDefault}))
case MetadataType.JAVAVERSION:
return items.map(v => ({ value: v, label: v.name, default: isDefault }));
default:
return sortedValues.map(v => ({ label: v.name }));
return items.map(v => ({ label: v.name, default: isDefault }));
}
}
}

public async getAvailableDependencies(serviceUrl: string, bootVersion: string): Promise<IDependency[]> {
const metadata = await this.ensureMetadata(serviceUrl);
Expand Down