Skip to content

Commit 13d9584

Browse files
authored
Task: create generation command (#5524)
1 parent ff0a4b5 commit 13d9584

File tree

9 files changed

+453
-366
lines changed

9 files changed

+453
-366
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface GeneratedOutputState {
2+
outputPath: string;
3+
clientClassName: string;
4+
}
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import TelemetryReporter from "@vscode/extension-telemetry";
2+
import * as path from "path";
3+
import * as vscode from "vscode";
4+
5+
import { extensionId, treeViewFocusCommand, treeViewId } from "../../constants";
6+
import { DependenciesViewProvider } from "../../dependenciesViewProvider";
7+
import { GenerationType, KiotaGenerationLanguage, KiotaPluginType } from "../../enums";
8+
import { ExtensionSettings, getExtensionSettings } from "../../extensionSettings";
9+
import { generateClient } from "../../generateClient";
10+
import { GeneratedOutputState } from "../../GeneratedOutputState";
11+
import { generatePlugin } from "../../generatePlugin";
12+
import { getLanguageInformation, getLanguageInformationForDescription } from "../../getLanguageInformation";
13+
import { setGenerationConfiguration } from "../../handlers/configurationHandler";
14+
import { clearDeepLinkParams, getDeepLinkParams } from "../../handlers/deepLinkParamsHandler";
15+
import { setWorkspaceGenerationType } from "../../handlers/workspaceGenerationTypeHandler";
16+
import { ConsumerOperation, generationLanguageToString, getLogEntriesForLevel, KiotaLogEntry, LogLevel } from "../../kiotaInterop";
17+
import { OpenApiTreeProvider } from "../../openApiTreeProvider";
18+
import { GenerateState, generateSteps } from "../../steps";
19+
import { getSanitizedString, getWorkspaceJsonDirectory, parseGenerationLanguage, parseGenerationType, parsePluginType, updateTreeViewIcons } from "../../util";
20+
import { isDeeplinkEnabled, transformToGenerationConfig } from "../../utilities/deep-linking";
21+
import { exportLogsAndShowErrors } from "../../utilities/logging";
22+
import { showUpgradeWarningMessage } from "../../utilities/messaging";
23+
import { Command } from "../Command";
24+
import { checkForSuccess, displayGenerationResults } from "./generation-util";
25+
26+
export class GenerateClientCommand extends Command {
27+
private _openApiTreeProvider: OpenApiTreeProvider;
28+
private _context: vscode.ExtensionContext;
29+
private _dependenciesViewProvider: DependenciesViewProvider;
30+
31+
constructor(openApiTreeProvider: OpenApiTreeProvider, context: vscode.ExtensionContext, dependenciesViewProvider: DependenciesViewProvider) {
32+
super();
33+
this._openApiTreeProvider = openApiTreeProvider;
34+
this._context = context;
35+
this._dependenciesViewProvider = dependenciesViewProvider;
36+
}
37+
38+
public getName(): string {
39+
return `${treeViewId}.generateClient`;
40+
}
41+
42+
public async execute(): Promise<void> {
43+
const deepLinkParams = getDeepLinkParams();
44+
const selectedPaths = this._openApiTreeProvider.getSelectedPaths();
45+
if (selectedPaths.length === 0) {
46+
await vscode.window.showErrorMessage(
47+
vscode.l10n.t("No endpoints selected, select endpoints first")
48+
);
49+
return;
50+
}
51+
52+
let languagesInformation = await getLanguageInformation(this._context);
53+
let availableStateInfo: Partial<GenerateState>;
54+
if (isDeeplinkEnabled(deepLinkParams)) {
55+
if (!deepLinkParams.name && this._openApiTreeProvider.apiTitle) {
56+
deepLinkParams.name = getSanitizedString(this._openApiTreeProvider.apiTitle);
57+
}
58+
availableStateInfo = transformToGenerationConfig(deepLinkParams);
59+
} else {
60+
const pluginName = getSanitizedString(this._openApiTreeProvider.apiTitle);
61+
availableStateInfo = {
62+
clientClassName: this._openApiTreeProvider.clientClassName,
63+
clientNamespaceName: this._openApiTreeProvider.clientNamespaceName,
64+
language: this._openApiTreeProvider.language,
65+
outputPath: this._openApiTreeProvider.outputPath,
66+
pluginName
67+
};
68+
}
69+
let config = await generateSteps(
70+
availableStateInfo,
71+
languagesInformation,
72+
deepLinkParams
73+
);
74+
setGenerationConfiguration(config);
75+
const generationType = parseGenerationType(config.generationType);
76+
const outputPath = typeof config.outputPath === "string"
77+
? config.outputPath
78+
: "./output";
79+
await showUpgradeWarningMessage(outputPath, this._context);
80+
if (!this._openApiTreeProvider.descriptionUrl) {
81+
await vscode.window.showErrorMessage(
82+
vscode.l10n.t("No description found, select a description first")
83+
);
84+
return;
85+
}
86+
87+
const settings = getExtensionSettings(extensionId);
88+
setWorkspaceGenerationType(config.generationType as string);
89+
let result;
90+
switch (generationType) {
91+
case GenerationType.Client:
92+
result = await this.generateClientAndRefreshUI(config, settings, outputPath, selectedPaths);
93+
break;
94+
case GenerationType.Plugin:
95+
result = await this.generatePluginAndRefreshUI(config, settings, outputPath, selectedPaths);
96+
break;
97+
case GenerationType.ApiManifest:
98+
result = await this.generateManifestAndRefreshUI(config, settings, outputPath, selectedPaths);
99+
break;
100+
default:
101+
await vscode.window.showErrorMessage(
102+
vscode.l10n.t("Invalid generation type")
103+
);
104+
return;
105+
}
106+
if (result && getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length === 0) {
107+
// Save state before opening the new window
108+
const outputState = {
109+
outputPath,
110+
config,
111+
clientClassName: config.clientClassName || config.pluginName
112+
};
113+
void this._context.workspaceState.update('generatedOutput', outputState as GeneratedOutputState);
114+
115+
const pathOfSpec = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-openapi.yml`);
116+
const pathPluginManifest = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-apiplugin.json`);
117+
if (deepLinkParams.source?.toLowerCase() === 'ttk') {
118+
try {
119+
await vscode.commands.executeCommand(
120+
'fx-extension.createprojectfromkiota',
121+
[
122+
pathOfSpec,
123+
pathPluginManifest,
124+
deepLinkParams.ttkContext ? deepLinkParams.ttkContext : undefined
125+
]
126+
);
127+
this._openApiTreeProvider.closeDescription();
128+
await updateTreeViewIcons(treeViewId, false);
129+
} catch (error) {
130+
const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey);
131+
reporter.sendTelemetryEvent("DeepLinked fx-extension.createprojectfromkiota", {
132+
"error": JSON.stringify(error)
133+
});
134+
}
135+
} else {
136+
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
137+
await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(config.workingDirectory ?? getWorkspaceJsonDirectory()), true);
138+
} else {
139+
await displayGenerationResults(this._openApiTreeProvider, config);
140+
}
141+
}
142+
143+
clearDeepLinkParams(); // Clear the state after the generation
144+
}
145+
}
146+
147+
private async generateManifestAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise<KiotaLogEntry[] | undefined> {
148+
const pluginTypes = KiotaPluginType.ApiManifest;
149+
const result = await vscode.window.withProgress({
150+
location: vscode.ProgressLocation.Notification,
151+
cancellable: false,
152+
title: vscode.l10n.t("Generating manifest...")
153+
}, async (progress, _) => {
154+
const start = performance.now();
155+
const result = await generatePlugin(
156+
this._context,
157+
this._openApiTreeProvider.descriptionUrl,
158+
outputPath,
159+
[pluginTypes],
160+
selectedPaths,
161+
[],
162+
typeof config.pluginName === "string"
163+
? config.pluginName
164+
: "ApiClient",
165+
settings.clearCache,
166+
settings.cleanOutput,
167+
settings.disableValidationRules,
168+
ConsumerOperation.Add,
169+
config.workingDirectory
170+
);
171+
const duration = performance.now() - start;
172+
const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0;
173+
const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey);
174+
reporter.sendRawTelemetryEvent(`${extensionId}.generateManifest.completed`, {
175+
"pluginType": pluginTypes.toString(),
176+
"errorsCount": errorsCount.toString(),
177+
}, {
178+
"duration": duration,
179+
});
180+
return result;
181+
});
182+
if (result) {
183+
const isSuccess = await checkForSuccess(result);
184+
if (!isSuccess) {
185+
await exportLogsAndShowErrors(result);
186+
}
187+
void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.'));
188+
}
189+
return result;
190+
}
191+
private async generatePluginAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise<KiotaLogEntry[] | undefined> {
192+
const pluginTypes = Array.isArray(config.pluginTypes) ? parsePluginType(config.pluginTypes) : [KiotaPluginType.ApiPlugin];
193+
const result = await vscode.window.withProgress({
194+
location: vscode.ProgressLocation.Notification,
195+
cancellable: false,
196+
title: vscode.l10n.t("Generating plugin...")
197+
}, async (progress, _) => {
198+
const start = performance.now();
199+
const result = await generatePlugin(
200+
this._context,
201+
this._openApiTreeProvider.descriptionUrl,
202+
outputPath,
203+
pluginTypes,
204+
selectedPaths,
205+
[],
206+
typeof config.pluginName === "string"
207+
? config.pluginName
208+
: "ApiClient",
209+
settings.clearCache,
210+
settings.cleanOutput,
211+
settings.disableValidationRules,
212+
ConsumerOperation.Add,
213+
config.workingDirectory
214+
);
215+
const duration = performance.now() - start;
216+
const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0;
217+
const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey);
218+
reporter.sendRawTelemetryEvent(`${extensionId}.generatePlugin.completed`, {
219+
"pluginType": pluginTypes.toString(),
220+
"errorsCount": errorsCount.toString(),
221+
}, {
222+
"duration": duration,
223+
});
224+
return result;
225+
});
226+
if (result) {
227+
const isSuccess = await checkForSuccess(result);
228+
if (!isSuccess) {
229+
await exportLogsAndShowErrors(result);
230+
}
231+
const deepLinkParams = getDeepLinkParams();
232+
const isttkIntegration = deepLinkParams.source?.toLowerCase() === 'ttk';
233+
if (!isttkIntegration) {
234+
void vscode.window.showInformationMessage(vscode.l10n.t('Plugin generated successfully.'));
235+
}
236+
}
237+
return result;
238+
}
239+
private async generateClientAndRefreshUI(config: Partial<GenerateState>, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise<KiotaLogEntry[] | undefined> {
240+
const language =
241+
typeof config.language === "string"
242+
? parseGenerationLanguage(config.language)
243+
: KiotaGenerationLanguage.CSharp;
244+
const result = await vscode.window.withProgress({
245+
location: vscode.ProgressLocation.Notification,
246+
cancellable: false,
247+
title: vscode.l10n.t("Generating client...")
248+
}, async (progress, _) => {
249+
const start = performance.now();
250+
const result = await generateClient(
251+
this._context,
252+
this._openApiTreeProvider.descriptionUrl,
253+
outputPath,
254+
language,
255+
selectedPaths,
256+
[],
257+
typeof config.clientClassName === "string"
258+
? config.clientClassName
259+
: "ApiClient",
260+
typeof config.clientNamespaceName === "string"
261+
? config.clientNamespaceName
262+
: "ApiSdk",
263+
settings.backingStore,
264+
settings.clearCache,
265+
settings.cleanOutput,
266+
settings.excludeBackwardCompatible,
267+
settings.disableValidationRules,
268+
settings.languagesSerializationConfiguration[language].serializers,
269+
settings.languagesSerializationConfiguration[language].deserializers,
270+
settings.structuredMimeTypes,
271+
settings.includeAdditionalData,
272+
ConsumerOperation.Add,
273+
config.workingDirectory
274+
);
275+
const duration = performance.now() - start;
276+
const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0;
277+
const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey);
278+
reporter.sendRawTelemetryEvent(`${extensionId}.generateClient.completed`, {
279+
"language": generationLanguageToString(language),
280+
"errorsCount": errorsCount.toString(),
281+
}, {
282+
"duration": duration,
283+
});
284+
return result;
285+
});
286+
287+
let languagesInformation = await getLanguageInformationForDescription(
288+
this._context,
289+
this._openApiTreeProvider.descriptionUrl,
290+
settings.clearCache
291+
);
292+
if (languagesInformation) {
293+
this._dependenciesViewProvider.update(languagesInformation, language);
294+
await vscode.commands.executeCommand(treeViewFocusCommand);
295+
}
296+
if (result) {
297+
const isSuccess = await checkForSuccess(result);
298+
if (!isSuccess) {
299+
await exportLogsAndShowErrors(result);
300+
}
301+
void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.'));
302+
}
303+
return result;
304+
}
305+
306+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as vscode from "vscode";
2+
3+
import { treeViewId } from "../../constants";
4+
import { KiotaLogEntry } from "../../kiotaInterop";
5+
import { OpenApiTreeProvider } from "../../openApiTreeProvider";
6+
import { getWorkspaceJsonPath, updateTreeViewIcons } from "../../util";
7+
import { loadWorkspaceFile } from "../../utilities/file";
8+
9+
export async function checkForSuccess(results: KiotaLogEntry[]) {
10+
for (const result of results) {
11+
if (result && result.message) {
12+
if (result.message.includes("Generation completed successfully")) {
13+
return true;
14+
}
15+
}
16+
}
17+
return false;
18+
}
19+
20+
export async function displayGenerationResults(openApiTreeProvider: OpenApiTreeProvider, config: any) {
21+
const clientNameOrPluginName = config.clientClassName || config.pluginName;
22+
openApiTreeProvider.refreshView();
23+
const workspaceJsonPath = getWorkspaceJsonPath();
24+
await loadWorkspaceFile({ fsPath: workspaceJsonPath }, openApiTreeProvider, clientNameOrPluginName);
25+
await vscode.commands.executeCommand('kiota.workspace.refresh');
26+
openApiTreeProvider.resetInitialState();
27+
await updateTreeViewIcons(treeViewId, false, true);
28+
}

0 commit comments

Comments
 (0)