Skip to content

Commit

Permalink
RESTEasy can be excluded if using code.quarkus.io
Browse files Browse the repository at this point in the history
Reads the data from code.quarkus.io or code.quarkus.redhat.com
to see which quarkus extensions are required.
This means if code.quarkus.io is used, then resteasy is not required.
Reads the Open API of the code quarkus instance to determine if
the option to prevent sample code being generated is present.
If it is, then it adds another step to the project
generation wizard to specify if sample code should be generated.

Closes redhat-developer#322

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 committed Jul 22, 2021
1 parent 846495c commit 81f78bd
Show file tree
Hide file tree
Showing 11 changed files with 1,515 additions and 1,870 deletions.
3,169 changes: 1,337 additions & 1,832 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@
]
},
"scripts": {
"preinstall": "npx npm-force-resolutions",
"vscode:prepublish": "webpack --mode production",
"compile": "webpack --mode none",
"watch": "webpack --mode development --watch --info-verbosity verbose",
Expand All @@ -268,7 +267,9 @@
"devDependencies": {
"@types/chai": "^4.2.3",
"@types/chai-fs": "^2.0.2",
"@types/ejs": "^3.0.5",
"@types/fs-extra": "^7.0.0",
"@types/js-yaml": "^4.0.0",
"@types/lodash": "^4.14.149",
"@types/mocha": "^5.2.6",
"@types/node": "^10.14.16",
Expand Down Expand Up @@ -303,6 +304,7 @@
"find-up": "^4.1.0",
"fs-extra": "^8.0.1",
"glob": "^7.1.4",
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"request": "^2.88.0",
"request-promise": "^4.2.4",
Expand Down
7 changes: 4 additions & 3 deletions src/definitions/QExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export class QExtension {
artifactId: string;
isRequired: boolean;

constructor(name: string, category: string, description: string, labels: string[], groupId: string, artifactId: string) {
constructor(name: string, category: string, description: string, labels: string[], groupId: string, artifactId: string, isRequired: boolean) {
this.name = name;
this.category = category;
this.description = description;
this.labels = labels;
this.groupId = groupId;
this.artifactId = artifactId;
this.isRequired = name === 'RESTEasy JAX-RS'; // 'RESTEasy JAX-RS' is included in every Quarkus project
this.isRequired = isRequired;
}

getGroupIdArtifactIdString() {
Expand All @@ -61,7 +61,7 @@ export function convertToQExtension(extension: APIExtension): QExtension {
artifactId = extension.id;
}
return new QExtension(extension.name, extension.category, extension.description,
extension.labels, groupId, artifactId);
extension.labels, groupId, artifactId, extension.default);
}

/**
Expand All @@ -76,4 +76,5 @@ export interface APIExtension {
shortName: string;
category: string;
order: Number;
default: boolean;
}
1 change: 1 addition & 0 deletions src/definitions/inputState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ProjectGenState extends State {
packageName: string;
resourceName: string;
targetDir: Uri;
isGenerateSampleCode: boolean;
}

export interface AddExtensionsState extends State {
Expand Down
21 changes: 18 additions & 3 deletions src/test/vscodeUiTest/ProjectGenerationWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* limitations under the License.
*/
import * as _ from 'lodash';

import { InputBox, QuickPickItem, Workbench, WebDriver, WebElement, By, until, Key } from 'vscode-extension-tester';
import { By, InputBox, Key, QuickPickItem, WebDriver, WebElement, Workbench } from 'vscode-extension-tester';
import { DialogHandler, OpenDialog } from 'vscode-extension-tester-native';

/**
Expand All @@ -30,7 +29,7 @@ export class ProjectGenerationWizard extends InputBox {
/**
* The number of steps the wizard has
*/
private lastStep: number = 7;
private lastStep: number = 8;

/**
* Opens the project generation wizard
Expand Down Expand Up @@ -78,6 +77,9 @@ export class ProjectGenerationWizard extends InputBox {
await wizard.confirm();
}
}
await wizard.focusQuickPick(0);
await wizard.next();
await wizard.focusQuickPick(0);
await wizard.next();

const dialog: OpenDialog = await DialogHandler.getOpenDialog();
Expand Down Expand Up @@ -213,6 +215,19 @@ export class ProjectGenerationWizard extends InputBox {
return (await this.getNthQuickPickItemInfo(n)).label;
}

/**
* Focuses on the quick pick with the given index
*
* @param n the index of the quick pick option to focus
* @returns when the quick pick is focused
*/
public async focusQuickPick(n: number): Promise<void> {
const quickPickToFocus: QuickPickItem = await this.findQuickPick(n);
while (!(await quickPickToFocus.getAttribute('class')).includes('focused')) {
await this.sendKeys(Key.DOWN);
}
}

/**
* Returns the `n`th quick pick item's `QuickPickItemInfo`
* @param n
Expand Down
58 changes: 32 additions & 26 deletions src/test/vscodeUiTest/suite/projectGenerationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,76 +100,82 @@ describe('Project generation tests', function() {
expect(resourceName).equals('GreetingResource');
await wizard.next();

await wizard.sendKeys(Key.DOWN, Key.UP);

expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
await wizard.sendKeys(Key.DOWN, Key.DOWN);
await wizard.focusQuickPick(0);
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('0 extensions selected');
await wizard.focusQuickPick(2);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('2 extensions selected');
await wizard.sendKeys(Key.DOWN, Key.DOWN, Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('3 extensions selected');
await wizard.sendKeys(Key.DOWN);
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
await wizard.focusQuickPick(3);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('2 extensions selected');
await wizard.sendKeys(Key.DOWN);
await wizard.focusQuickPick(1);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
await wizard.focusQuickPick(1);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('0 extensions selected');
await wizard.cancel();
});

/**
* Tests if the project generation wizard has correct
* step values at the wizard's title bar: (1/7), (2/7)
* step values at the wizard's title bar: (1/8), (2/8)
*/
it('should have correct step values', async function() {
this.timeout(30000);
const wizard: ProjectGenerationWizard = await ProjectGenerationWizard.openWizard(driver);
expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('3/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('3/7');
expect(await wizard.getInputBoxTitle()).to.have.string('4/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('4/7');
expect(await wizard.getInputBoxTitle()).to.have.string('5/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('5/7');
expect(await wizard.getInputBoxTitle()).to.have.string('6/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('6/7');
expect(await wizard.getInputBoxTitle()).to.have.string('7/8');
await wizard.focusQuickPick(0);
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('7/7');
expect(await wizard.getInputBoxTitle()).to.have.string('8/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('7/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('6/7');
expect(await wizard.getInputBoxTitle()).to.have.string('6/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('5/7');
expect(await wizard.getInputBoxTitle()).to.have.string('5/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('4/7');
expect(await wizard.getInputBoxTitle()).to.have.string('4/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('3/7');
expect(await wizard.getInputBoxTitle()).to.have.string('3/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;

await wizard.cancel();
Expand Down
86 changes: 86 additions & 0 deletions src/utils/codeQuarkusApiUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright 2021 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IncomingMessage } from "http";
import * as https from "https";
import * as yaml from "js-yaml";
import * as path from "path";
import { URL } from "url";
import { QuarkusConfig } from "../QuarkusConfig";

const HTTP_MATCHER = new RegExp('^http://');

export interface CodeQuarkusFunctionality {
canExcludeSampleCode: boolean;
}

/**
* Returns the capabilities of the Code Quarkus API instance that is defined in the user settings
*
* @returns the capabilities of the Code Quarkus API instance that is defined in the user settings
* @throws if something goes wrong when getting the functionality from OpenAPI
*/
export async function getCodeQuarkusApiFunctionality(): Promise<CodeQuarkusFunctionality> {
let oldOpenApiUrl: string = path.dirname(QuarkusConfig.getApiUrl()) + '/openapi';
let newOpenApiUrl: string = path.dirname(QuarkusConfig.getApiUrl()) + '/q/openapi';
oldOpenApiUrl = oldOpenApiUrl.replace(HTTP_MATCHER, "https://");
newOpenApiUrl = oldOpenApiUrl.replace(HTTP_MATCHER, "https://");
let openApiYaml: string;
try {
openApiYaml = await httpsGet(newOpenApiUrl);
} catch {
openApiYaml = await httpsGet(oldOpenApiUrl);
}
const openApiData: any = yaml.load(openApiYaml);

return {
canExcludeSampleCode: openApiData?.paths?.['/api/download']?.get?.parameters?.filter(p => p?.name === 'ne').length > 0
} as CodeQuarkusFunctionality;
}

/**
* Returns a set of capabilities that are implemented by all Code Quarkus APIs
*
* @returns a set of capabilities that are implemented by all Code Quarkus APIs
*/
export function getDefaultFunctionality() {
return {
canExcludeSampleCode: false
} as CodeQuarkusFunctionality;
}

/**
* Returns the GET response body if the code is 200 and rejects otherwise
*
* @param url URL to GET
* @returns the response body if the code is 200 and rejects otherwise
* @throws if anything goes wrong (not 200 response, any other errors during get)
*/
async function httpsGet(url: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
https.get(url, (res: IncomingMessage) => {
if (res.statusCode === 301 || res.statusCode === 302) {
httpsGet(new URL(url).origin + res.headers.location) //
.then(resolve, reject);
} else if (res.statusCode !== 200) {
reject(`${res.statusCode}: ${res.statusMessage}`);
}
res.on('data', (d: Buffer) => {
resolve(d.toString('utf8'));
});
})
.on('error', reject);
});
}
1 change: 1 addition & 0 deletions src/utils/requestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export async function downloadProject(state: ProjectGenState): Promise<ZipFile>
`a=${state.artifactId}&` +
`v=${state.projectVersion}&` +
`c=${state.packageName}.${state.resourceName}&` +
`${(state.isGenerateSampleCode === undefined ? '' : `ne=${!state.isGenerateSampleCode}&`)}` +
`e=${chosenIds.join('&e=')}`;

const buffer: Buffer = await tryGetProjectBuffer(qProjectUrl);
Expand Down
2 changes: 1 addition & 1 deletion src/wizards/generateProject/ExtensionsPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export class ExtensionsPicker {
const numSelected: number = this.selectedExtensions.length;
items.push({
type: Type.Stop,
label: `$(tasklist) ${numSelected} extension${numSelected > 1 ? 's' : ''} selected`,
label: `$(tasklist) ${numSelected} extension${numSelected !== 1 ? 's' : ''} selected`,
description: '',
detail: 'Press <Enter> to continue'
});
Expand Down
33 changes: 30 additions & 3 deletions src/wizards/generateProject/generationWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { BuildToolName, INPUT_TITLE } from '../../definitions/constants';
import { ProjectGenState } from '../../definitions/inputState';
import { QExtension } from '../../definitions/QExtension';
import { QuarkusContext } from '../../QuarkusContext';
import { MultiStepInput } from '../../utils/multiStepUtils';
import { CodeQuarkusFunctionality, getCodeQuarkusApiFunctionality, getDefaultFunctionality } from '../../utils/codeQuarkusApiUtils';
import { MultiStepInput, QuickPickParameters } from '../../utils/multiStepUtils';
import { downloadProject } from '../../utils/requestUtils';
import { ExtensionsPicker } from './ExtensionsPicker';
import { validateArtifactId, validateGroupId, validatePackageName, validateResourceName, validateVersion } from './validateInput';
Expand All @@ -24,8 +25,15 @@ import { validateArtifactId, validateGroupId, validatePackageName, validateResou
*/
export async function generateProjectWizard() {

let apiCapabilities: CodeQuarkusFunctionality;
try {
apiCapabilities = await getCodeQuarkusApiFunctionality();
} catch (e) {
apiCapabilities = getDefaultFunctionality();
}

const state: Partial<ProjectGenState> = {
totalSteps: 7
totalSteps: 7 + (apiCapabilities.canExcludeSampleCode ? 1 : 0)
};

async function collectInputs(state: Partial<ProjectGenState>) {
Expand Down Expand Up @@ -139,7 +147,26 @@ export async function generateProjectWizard() {
prompt: 'Your resource name',
validate: validateResourceName
});
return (input: MultiStepInput) => ExtensionsPicker.createExtensionsPicker(input, state, { showLastUsed: true, showRequiredExtensions: true, allowZeroExtensions: true });
return (input: MultiStepInput) => ExtensionsPicker.createExtensionsPicker(
input, state, { showLastUsed: true, showRequiredExtensions: true, allowZeroExtensions: true },
(apiCapabilities.canExcludeSampleCode ? inputGenerateSampleCode: undefined));
}

async function inputGenerateSampleCode(input: MultiStepInput, state: Partial<ProjectGenState>) {
const YES: string = 'Include sample code';
const NO: string = 'Do not include sample code';
const quickPickItems: QuickPickItem[] = [
{label: YES, picked: true},
{label: NO}
];

state.isGenerateSampleCode = (await input.showQuickPick<QuickPickItem, QuickPickParameters<QuickPickItem>>({
title: INPUT_TITLE,
placeholder: 'Should sample code be included? Additional dependencies may be added along with the sample.',
step: input.getStepNumber(),
totalSteps: state.totalSteps,
items: quickPickItems,
})).label === YES;
}

await collectInputs(state);
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
},
"linterOptions": {
"exclude": [
"test-resources"
"test-resources",
"out/**/*"
]
}
}

0 comments on commit 81f78bd

Please sign in to comment.