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

Fix: Kiota extension works without a workspace folder open #4958

Merged
merged 28 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
498c842
remove workspace warnings
ElinorW Jul 2, 2024
28fd20c
update default output path if no workspace is present
ElinorW Jul 2, 2024
1b06ef6
update state after generation
ElinorW Jul 2, 2024
fda85e1
fix switch case
ElinorW Jul 11, 2024
b4f501f
add workspace path function
ElinorW Jul 11, 2024
4a3fe3d
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 11, 2024
4c4ede7
return deleted line
ElinorW Jul 11, 2024
62e57c4
fix: fixes workspace loading when vscode does not have a folder open …
baywet Jul 12, 2024
8bfff81
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 12, 2024
522d2a9
Merge branch 'elinor/open-output' of https://github.com/microsoft/kio…
ElinorW Jul 12, 2024
935794c
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 15, 2024
f5749db
udapte workspace provider
ElinorW Jul 15, 2024
65228b0
uddate cwd
ElinorW Jul 15, 2024
94364e9
Merge branch 'elinor/open-output' of https://github.com/microsoft/kio…
ElinorW Jul 15, 2024
cae8b1c
update directory function
ElinorW Jul 15, 2024
9ffecd3
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
baywet Jul 15, 2024
1b2c00d
Merge branch 'elinor/open-output' of https://github.com/microsoft/kio…
ElinorW Jul 16, 2024
6561c88
update get workspace directory
ElinorW Jul 16, 2024
90199f5
update workspace function
ElinorW Jul 16, 2024
51f6b44
correct return types
ElinorW Jul 16, 2024
207b780
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 16, 2024
a6ab934
add appPackage as default output path
ElinorW Jul 17, 2024
517427b
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 17, 2024
da954ed
update indentation
ElinorW Jul 18, 2024
0118d78
modify outputPath
ElinorW Jul 18, 2024
05cda48
Merge branch 'elinor/open-output' of https://github.com/microsoft/kio…
ElinorW Jul 18, 2024
927286a
add kiota files for selected output path
ElinorW Jul 26, 2024
6046a3a
Merge branch 'elinor/add-kiota-workspace' into elinor/open-output
ElinorW Jul 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 55 additions & 46 deletions vscode/microsoft-kiota/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import { getLanguageInformation, getLanguageInformationForDescription } from "./
import { DependenciesViewProvider } from "./dependenciesViewProvider";
import { updateClients } from "./updateClients";
import { ExtensionSettings, getExtensionSettings } from "./extensionSettings";
import { KiotaWorkspace } from "./workspaceTreeProvider";
import { loadTreeView } from "./workspaceTreeProvider";
import { generatePlugin } from "./generatePlugin";
import { CodeLensProvider } from "./codelensProvider";
import { KIOTA_DIRECTORY, KIOTA_WORKSPACE_FILE, dependenciesInfo, extensionId, statusBarCommandId, treeViewFocusCommand, treeViewId } from "./constants";
import { isClientType, isPluginType, updateTreeViewIcons } from "./util";
import { getWorkspaceJsonDirectory, getWorkspaceJsonPath, isClientType, isPluginType, updateTreeViewIcons } from "./util";

let kiotaStatusBarItem: vscode.StatusBarItem;
let kiotaOutputChannel: vscode.LogOutputChannel;
Expand All @@ -38,6 +38,11 @@ let clientOrPluginObject: ClientOrPluginProperties;
let workspaceGenerationType: string;
let config: Partial<GenerateState>;

interface GeneratedOutputState {
outputPath: string;
clientClassName: string;
}

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export async function activate(
Expand All @@ -46,13 +51,12 @@ export async function activate(
kiotaOutputChannel = vscode.window.createOutputChannel("Kiota", {
log: true,
});
const workspaceJsonPath = path.join(vscode.workspace.workspaceFolders?.map(folder => folder.uri.fsPath).join('') || '', KIOTA_DIRECTORY, KIOTA_WORKSPACE_FILE);
const openApiTreeProvider = new OpenApiTreeProvider(context, () => getExtensionSettings(extensionId));
const dependenciesInfoProvider = new DependenciesViewProvider(
context.extensionUri
);
const reporter = new TelemetryReporter(context.extension.packageJSON.telemetryInstrumentationKey);
new KiotaWorkspace(context);
await loadTreeView(context);
let codeLensProvider = new CodeLensProvider();
context.subscriptions.push(
vscode.window.registerUriHandler({
Expand Down Expand Up @@ -127,15 +131,7 @@ export async function activate(
);
return;
}
if (
!vscode.workspace.workspaceFolders ||
vscode.workspace.workspaceFolders.length === 0
) {
await vscode.window.showErrorMessage(
vscode.l10n.t("No workspace folder found, open a folder first")
);
return;
}

let languagesInformation = await getLanguageInformation(context);
config = await generateSteps(
{
Expand All @@ -157,27 +153,50 @@ export async function activate(
);
return;
}

const settings = getExtensionSettings(extensionId);
workspaceGenerationType = config.generationType as string;
let result;
switch (generationType) {
case GenerationType.Client:
await generateClientAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
result = await generateClientAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
case GenerationType.Plugin:
await generatePluginAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
result = await generatePluginAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
case GenerationType.ApiManifest:
await generateManifestAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
result = await generateManifestAndRefreshUI(config, settings, outputPath, selectedPaths);
break;
default:
await vscode.window.showErrorMessage(
vscode.l10n.t("Invalid generation type")
);
break;
return;
}
if (result && getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length === 0) {
Copy link
Member

Choose a reason for hiding this comment

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

+1 on this

Do we also want an else clause that displays the reaons for the generation failure by picking the log entry that matches LogLevel.critical?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, we can display the error in the case of LogLevel.critical

// Save state before opening the new window
void context.workspaceState.update('generatedOutput', {
outputPath,
config,
clientClassName: config.clientClassName || config.pluginName
} as GeneratedOutputState);
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(getWorkspaceJsonDirectory()), true);
} else {
await displayGenerationResults(context, openApiTreeProvider, config, outputPath);
}
}
}
),
vscode.workspace.onDidChangeWorkspaceFolders(async () => {
baywet marked this conversation as resolved.
Show resolved Hide resolved
const generatedOutput = context.workspaceState.get<GeneratedOutputState>('generatedOutput');
if (generatedOutput) {
const { outputPath} = generatedOutput;
await displayGenerationResults(context, openApiTreeProvider, config, outputPath);
// Clear the state
void context.workspaceState.update('generatedOutput', undefined);
}
}),
registerCommandWithTelemetry(reporter,
`${treeViewId}.searchOrOpenApiDescription`,
async () => {
Expand Down Expand Up @@ -276,7 +295,7 @@ export async function activate(
}),
);

async function generateManifestAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<void> {
async function generateManifestAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<KiotaLogEntry[]| undefined> {
const pluginTypes = KiotaPluginType.ApiManifest;
const result = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
Expand Down Expand Up @@ -311,15 +330,11 @@ export async function activate(
});
if (result)
{
await checkForSuccess(result);
openApiTreeProvider.refreshView();
openApiTreeProvider.setSelectionChanged(false);
await loadLockFile({fsPath: workspaceJsonPath}, openApiTreeProvider, config.pluginName);
await updateTreeViewIcons(treeViewId, false, true);
await exportLogsAndShowErrors(result);
}
return result;
}
async function generatePluginAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<void> {
async function generatePluginAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<KiotaLogEntry[] | undefined> {
const pluginTypes = Array.isArray(config.pluginTypes) ? parsePluginType(config.pluginTypes) : [KiotaPluginType.ApiPlugin];
const result = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
Expand Down Expand Up @@ -354,15 +369,11 @@ export async function activate(
});
if (result)
{
await checkForSuccess(result);
openApiTreeProvider.refreshView();
openApiTreeProvider.setSelectionChanged(false);
await loadLockFile({fsPath: workspaceJsonPath}, openApiTreeProvider, config.pluginName);
await updateTreeViewIcons(treeViewId, false, true);
await exportLogsAndShowErrors(result);
}
return result;
}
async function generateClientAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<void> {
async function generateClientAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]):Promise<KiotaLogEntry[] | undefined> {
const language =
typeof config.language === "string"
? parseGenerationLanguage(config.language)
Expand Down Expand Up @@ -417,22 +428,20 @@ export async function activate(
dependenciesInfoProvider.update(languagesInformation, language);
await vscode.commands.executeCommand(treeViewFocusCommand);
}
if (typeof config.outputPath === "string" && !openApiTreeProvider.isLockFileLoaded &&
vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 &&
result && getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length === 0) {
const WORKSPACE_FOLDER = vscode.workspace.workspaceFolders[0].uri.fsPath;
const KIOTA_WORKSPACE_PATH = path.join(WORKSPACE_FOLDER, KIOTA_DIRECTORY, KIOTA_WORKSPACE_FILE);
await openApiTreeProvider.loadLockFile(KIOTA_WORKSPACE_PATH, config.clientClassName);
}
if (result)
{
await checkForSuccess(result);
openApiTreeProvider.refreshView();
openApiTreeProvider.setSelectionChanged(false);
await loadLockFile({fsPath: workspaceJsonPath}, openApiTreeProvider, config.clientClassName);
await updateTreeViewIcons(treeViewId, false, true);
await exportLogsAndShowErrors(result);
}
return result;
}

async function displayGenerationResults(context: vscode.ExtensionContext, openApiTreeProvider: OpenApiTreeProvider, config: any, outputPath: string) {
const clientNameOrPluginName = config.clientClassName || config.pluginName;
openApiTreeProvider.refreshView();
openApiTreeProvider.setSelectionChanged(false);
const workspaceJsonPath = getWorkspaceJsonPath();
await loadLockFile({fsPath: workspaceJsonPath}, openApiTreeProvider, clientNameOrPluginName );
await updateTreeViewIcons(treeViewId, false, true);
}
async function regenerateClient(clientKey: string, clientObject:any, settings: ExtensionSettings, selectedPaths?: string[]): Promise<void> {
const language =
Expand Down
3 changes: 2 additions & 1 deletion vscode/microsoft-kiota/src/kiotaInterop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import * as vscode from "vscode";
import * as cp from 'child_process';
import * as rpc from 'vscode-jsonrpc/node';
import { ensureKiotaIsPresent, getKiotaPath } from './kiotaInstall';
import { getWorkspaceJsonDirectory } from "./util";

export async function connectToKiota<T>(context: vscode.ExtensionContext, callback:(connection: rpc.MessageConnection) => Promise<T | undefined>): Promise<T | undefined> {
const kiotaPath = getKiotaPath(context);
await ensureKiotaIsPresent(context);
const childProcess = cp.spawn(kiotaPath, ["rpc"],{
cwd: vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined,
cwd: getWorkspaceJsonDirectory(),
env: {
...process.env,
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down
29 changes: 24 additions & 5 deletions vscode/microsoft-kiota/src/steps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode';
import { QuickPickItem, window, Disposable, QuickInputButton, QuickInput, QuickInputButtons, workspace, l10n, Uri, OpenDialogOptions } from 'vscode';
import { allGenerationLanguages, generationLanguageToString, KiotaSearchResultItem, LanguagesInformation, maturityLevelToString } from './kiotaInterop';
import * as vscode from 'vscode';
import { findAppPackageDirectory, getWorkspaceJsonDirectory } from './util';

export async function filterSteps(existingFilter: string, filterCallback: (searchQuery: string) => void) {
const state = {} as Partial<BaseStepsState>;
Expand Down Expand Up @@ -107,17 +108,32 @@ export async function generateSteps(existingConfiguration: Partial<GenerateState

if(typeof state.outputPath === 'string') {
state.outputPath = workspace.asRelativePath(state.outputPath);
}
const workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '';

}

let workspaceFolder = getWorkspaceJsonDirectory();
const appPackagePath = findAppPackageDirectory(workspaceFolder);
if (appPackagePath) {
workspaceFolder = appPackagePath;
}

let step = 1;
let totalSteps = 4;

const folderSelectionOption = l10n.t('Browse your output directory');
const inputOptions = [
let inputOptions = [
{label: l10n.t('Default folder'), description: workspaceFolder },
{label: folderSelectionOption}
];

function updateWorkspaceFolder(name: string | undefined) {
if (name && (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0)) {
workspaceFolder = getWorkspaceJsonDirectory(name);
inputOptions = [
{ label: l10n.t('Default folder'), description: workspaceFolder },
{ label: folderSelectionOption }
];
}
}
async function inputGenerationType(input: MultiStepInput, state: Partial<GenerateState>) {
const items = [l10n.t('Generate an API client'), l10n.t('Generate a plugin'), l10n.t('Generate an API manifest')];
const option = await input.showQuickPick({
Expand Down Expand Up @@ -153,6 +169,7 @@ export async function generateSteps(existingConfiguration: Partial<GenerateState
validate: validateIsNotEmpty,
shouldResume: shouldResume
});
updateWorkspaceFolder(state.clientClassName);
return (input: MultiStepInput) => inputClientNamespaceName(input, state);
}
async function inputClientNamespaceName(input: MultiStepInput, state: Partial<GenerateState>) {
Expand Down Expand Up @@ -233,6 +250,7 @@ export async function generateSteps(existingConfiguration: Partial<GenerateState
validate: validateIsNotEmpty,
shouldResume: shouldResume
});
updateWorkspaceFolder(state.pluginName);
return (input: MultiStepInput) => inputPluginType(input, state);
}
async function inputPluginType(input: MultiStepInput, state: Partial<GenerateState>) {
Expand Down Expand Up @@ -293,6 +311,7 @@ export async function generateSteps(existingConfiguration: Partial<GenerateState
validate: validateIsNotEmpty,
shouldResume: shouldResume
});
updateWorkspaceFolder(state.pluginName);
return (input: MultiStepInput) => inputManifestOutputPath(input, state);
}
async function inputManifestOutputPath(input: MultiStepInput, state: Partial<GenerateState>) {
Expand Down
50 changes: 49 additions & 1 deletion vscode/microsoft-kiota/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as vscode from 'vscode';
import { APIMANIFEST, CLIENT, CLIENTS, PLUGIN, PLUGINS } from './constants';
import * as path from 'path';
import * as fs from 'fs';
import { APIMANIFEST, CLIENT, CLIENTS, KIOTA_DIRECTORY, KIOTA_WORKSPACE_FILE, PLUGIN, PLUGINS } from './constants';

const clientTypes = [CLIENT, CLIENTS];
const pluginTypes = [PLUGIN, PLUGINS, APIMANIFEST];
Expand All @@ -17,4 +19,50 @@ export async function updateTreeViewIcons(treeViewId: string, showIcons: boolean
if (showRegenerateIcon !== undefined) {
await vscode.commands.executeCommand('setContext', `${treeViewId}.showRegenerateIcon`, showRegenerateIcon);
}
}

export function getWorkspaceJsonPath(): string {
return path.join(getWorkspaceJsonDirectory(),KIOTA_DIRECTORY, KIOTA_WORKSPACE_FILE);
};

export function getWorkspaceJsonDirectory(clientNameOrPluginName?: string): string {
const baseDir = path.join(
vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
? vscode.workspace.workspaceFolders[0].uri.fsPath
: process.env.HOME ?? process.env.USERPROFILE ?? process.cwd()
);

let workspaceFolder = !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0
? path.join(baseDir, 'kiota', clientNameOrPluginName ?? '')
: baseDir;

if (!fs.existsSync(workspaceFolder)) {
fs.mkdirSync(workspaceFolder, { recursive: true });
}
return workspaceFolder;
}

export function findAppPackageDirectory(directory: string): string | null {
if (!fs.existsSync(directory)) {
return null;
}

const entries = fs.readdirSync(directory, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(directory, entry.name);

if (entry.isDirectory()) {
if (entry.name === 'appPackage') {
return fullPath;
}

const subDirectory = findAppPackageDirectory(fullPath);
if (subDirectory) {
return subDirectory;
}
}
}

return null;
}
Loading
Loading