From f9d8a91e2f26703fd99e7696e998309abbafc7ba Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Thu, 25 Sep 2025 17:56:41 +1000 Subject: [PATCH 01/18] refactor: add api clients and refactor WSEditor, ..CommandContent, ..SwaggerPicker, WorkspaceSelector --- src/web/src/services/commandApi.ts | 163 ++++++++++++++ src/web/src/services/index.ts | 4 + src/web/src/services/specsApi.ts | 129 +++++++++++ src/web/src/services/workspaceApi.ts | 212 ++++++++++++++++++ src/web/src/views/workspace/WSEditor.tsx | 167 +++++--------- .../workspace/WSEditorCommandContent.tsx | 164 ++++++-------- .../views/workspace/WSEditorSwaggerPicker.tsx | 203 +++++++---------- .../src/views/workspace/WorkspaceSelector.tsx | 103 +++------ 8 files changed, 735 insertions(+), 410 deletions(-) create mode 100644 src/web/src/services/commandApi.ts create mode 100644 src/web/src/services/index.ts create mode 100644 src/web/src/services/specsApi.ts create mode 100644 src/web/src/services/workspaceApi.ts diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts new file mode 100644 index 00000000..41208d05 --- /dev/null +++ b/src/web/src/services/commandApi.ts @@ -0,0 +1,163 @@ +import axios from "axios"; + +/** + * Service for command-related API operations + */ +export class CommandApiService { + /** + * Get command details + */ + static async getCommand(leafUrl: string): Promise { + const res = await axios.get(leafUrl); + return res.data; + } + + /** + * Get commands for a resource URL + */ + static async getCommandsForResource(resourceUrl: string): Promise { + const res = await axios.get(`${resourceUrl}/Commands`); + return res.data; + } + + /** + * Delete resource + */ + static async deleteResource(resourceUrl: string): Promise { + await axios.delete(resourceUrl); + } + + /** + * Update command properties + */ + static async updateCommand(leafUrl: string, data: any): Promise { + const res = await axios.patch(leafUrl, data); + return res.data; + } + + /** + * Rename command + */ + static async renameCommand(leafUrl: string, newName: string): Promise { + const res = await axios.post(`${leafUrl}/Rename`, { name: newName }); + return res.data; + } + + /** + * Update command examples + */ + static async updateCommandExamples(leafUrl: string, examples: any[]): Promise { + const res = await axios.patch(leafUrl, { examples }); + return res.data; + } + + /** + * Generate examples from swagger + */ + static async generateSwaggerExamples(leafUrl: string): Promise { + const res = await axios.post(`${leafUrl}/GenerateExamples`, { source: "swagger" }); + return res.data.map((v: any) => ({ + name: v.name, + commands: v.commands, + })); + } + + /** + * Add subcommands + */ + static async addSubcommands(resourceUrl: string, data: any): Promise { + await axios.post(resourceUrl, data); + } + + /** + * Update command outputs + */ + static async updateCommandOutputs(leafUrl: string, outputs: any[]): Promise { + const res = await axios.patch(leafUrl, { outputs }); + return res.data; + } + + /** + * Update command argument + */ + static async updateCommandArgument(argumentUrl: string, data: any): Promise { + await axios.patch(argumentUrl, data); + } + + /** + * Update argument by ID + */ + static async updateArgumentById(argId: string, data: any): Promise { + await axios.patch(argId, data); + } + + /** + * Flatten argument structure + */ + static async flattenArgument(flattenUrl: string, data?: any): Promise { + if (data) { + await axios.post(flattenUrl, data); + } else { + await axios.post(flattenUrl); + } + } + + /** + * Unwrap class argument + */ + static async unwrapClassArgument(flattenUrl: string): Promise { + await axios.post(flattenUrl); + } + + /** + * Create a subresource for a command resource + */ + static async createSubresource( + subresourceUrl: string, + data: { + commandGroupName: string; + refArgsOptions: { [argVar: string]: string[] }; + arg: string; + }, + ): Promise { + try { + const response = await axios.post(subresourceUrl, data); + return response.data; + } catch (err: any) { + ApiErrorHandler.handleApiError(err, "Failed to create subresource"); + } + } +} + +/** + * Error handling utilities for API operations + */ +export class ApiErrorHandler { + /** + * Extract error message from axios error response + */ + static getErrorMessage(err: any): string { + if (err.response?.data?.message) { + const data = err.response.data; + const details = data.details ? `: ${JSON.stringify(data.details)}` : ""; + return `ResponseError: ${data.message}${details}`; + } + return "An unexpected error occurred"; + } + + /** + * Check if error is a specific HTTP status code + */ + static isHttpError(err: any, statusCode: number): boolean { + return err.response?.status === statusCode; + } + + /** + * Handle common API error scenarios + */ + static handleApiError(err: any, context: string = ""): never { + console.error(context, err); + const message = this.getErrorMessage(err); + throw new Error(message); + } +} diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts new file mode 100644 index 00000000..463f4a35 --- /dev/null +++ b/src/web/src/services/index.ts @@ -0,0 +1,4 @@ +// Barrel export for all API services +export { WorkspaceApiService, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; +export { SpecsApiService, SpecsHelper, type Plane, type Resource } from "./specsApi"; +export { CommandApiService, ApiErrorHandler } from "./commandApi"; diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts new file mode 100644 index 00000000..d490e878 --- /dev/null +++ b/src/web/src/services/specsApi.ts @@ -0,0 +1,129 @@ +import axios from "axios"; + +// Types for specs/planes/modules operations +export interface Plane { + name: string; + displayName: string; + moduleOptions?: string[]; +} + +export interface Resource { + id: string; + version: string; +} + +/** + * Service for API specifications, planes, modules, and resource providers + */ +export class SpecsApiService { + /** + * Get all available planes + */ + static async getPlanes(): Promise { + const res = await axios.get(`/AAZ/Specs/Planes`); + return res.data.map((v: any) => ({ + name: v.name, + displayName: v.displayName, + moduleOptions: undefined, + })); + } + + /** + * Get plane names only (used for client configurable plane detection) + */ + static async getPlaneNames(): Promise { + const res = await axios.get(`/AAZ/Specs/Planes`); + return res.data.map((v: any) => v.name); + } + + /** + * Get modules for a specific plane + */ + static async getModulesForPlane(planeName: string): Promise { + const res = await axios.get(`/Swagger/Specs/${planeName}`); + return res.data.map((v: any) => v.url); + } + + /** + * Get resource providers for a specific module + */ + static async getResourceProviders(moduleUrl: string): Promise { + const res = await axios.get(`${moduleUrl}/ResourceProviders`); + return res.data.map((v: any) => v.url); + } + + /** + * Get resources for a specific resource provider + */ + static async getResources(resourceProviderUrl: string): Promise { + const res = await axios.get(`${resourceProviderUrl}/Resources`); + return res.data; + } + + /** + * Get swagger modules for a plane + */ + static async getSwaggerModules(plane: string): Promise { + const res = await axios.get(`/Swagger/Specs/${plane}`); + return res.data.map((v: any) => v.url); + } + + /** + * Get resource providers for a module (with optional type filter) + */ + static async getResourceProvidersWithType(moduleUrl: string, type?: string): Promise { + let url = `${moduleUrl}/ResourceProviders`; + if (type) { + url += `?type=${type}`; + } + const res = await axios.get(url); + return res.data.map((v: any) => v.url); + } + + /** + * Get resources for a resource provider (returns raw data) + */ + static async getProviderResources(resourceProviderUrl: string): Promise { + const res = await axios.get(`${resourceProviderUrl}/Resources`); + return res.data; + } + + /** + * Filter resources by plane + */ + static async filterResourcesByPlane(plane: string, resourceIds: string[]): Promise { + const filterBody = { resources: resourceIds }; + const res = await axios.post(`/AAZ/Specs/Resources/${plane}/Filter`, filterBody); + return res.data; + } +} + +/** + * Helper functions for working with specs + */ +export class SpecsHelper { + /** + * Remove common prefix from option strings + */ + static removeCommonPrefix(options: string[], prefix: string): string[] { + return options.map((option) => option.replace(prefix, "")); + } + + /** + * Check if a plane is client configurable (not in built-in planes) + */ + static async isClientConfigurablePlane(planeName: string): Promise { + const planeNames = await SpecsApiService.getPlaneNames(); + return !planeNames.includes(planeName); + } + + /** + * Build full resource provider URL + */ + static buildResourceProviderUrl(plane: string, modNames: string[], rpName: string, source: string): string { + const basePath = `/Swagger/Specs/${plane}/${modNames.join("/")}`; + const resourceProviderPath = `/ResourceProviders/${rpName}`; + const suffix = source.toLowerCase() === "typespec" ? "/TypeSpec" : ""; + return `${basePath}${resourceProviderPath}${suffix}`; + } +} diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts new file mode 100644 index 00000000..d4104922 --- /dev/null +++ b/src/web/src/services/workspaceApi.ts @@ -0,0 +1,212 @@ +import axios from "axios"; + +// Types for workspace operations +export interface Workspace { + name: string; + plane: string | null; + modNames: string | null; + resourceProvider: string | null; + lastModified: Date | null; + url: any | null; + folder: string | null; +} + +export interface CreateWorkspaceData { + name: string; + plane: string; + modNames: string; + resourceProvider: string; + source: string; +} + +export interface ClientConfig { + version: string; + endpointTemplates?: { [key: string]: string }; + endpointResource?: string; + auth: any; +} + +/** + * Service for workspace-related API operations + */ +export class WorkspaceApiService { + /** + * Get all workspaces + */ + static async getWorkspaces(): Promise { + const res = await axios.get("/AAZ/Editor/Workspaces"); + return res.data.map((option: any) => ({ + name: option.name, + lastModified: new Date(option.updated * 1000), + url: option.url, + plane: option.plane, + folder: option.folder, + })); + } + + /** + * Create a new workspace + */ + static async createWorkspace(data: CreateWorkspaceData): Promise { + const res = await axios.post("/AAZ/Editor/Workspaces", data); + const workspace = res.data; + return { + name: workspace.name, + plane: workspace.plane, + modNames: workspace.modNames, + resourceProvider: workspace.resourceProvider, + lastModified: new Date(workspace.updated * 1000), + url: workspace.url, + folder: workspace.folder, + }; + } + + /** + * Get workspace details + */ + static async getWorkspace(workspaceUrl: string): Promise { + const res = await axios.get(workspaceUrl); + return res.data; + } + + /** + * Delete a workspace + */ + static async deleteWorkspace(workspaceName: string): Promise { + const nodeUrl = `/AAZ/Editor/Workspaces/${workspaceName}`; + await axios.delete(nodeUrl); + } + + /** + * Rename a workspace + */ + static async renameWorkspace(workspaceUrl: string, newName: string): Promise<{ name: string }> { + const res = await axios.post(`${workspaceUrl}/Rename`, { name: newName }); + return res.data; + } + + /** + * Get workspace client config + */ + static async getWorkspaceClientConfig(workspaceUrl: string): Promise { + try { + const res = await axios.get(`${workspaceUrl}/ClientConfig`); + const clientConfig: ClientConfig = { + version: res.data.version, + endpointTemplates: undefined, + endpointResource: undefined, + auth: res.data.auth, + }; + + if (res.data.endpoints.type === "template") { + clientConfig.endpointTemplates = {}; + res.data.endpoints.templates.forEach((value: any) => { + clientConfig.endpointTemplates![value.cloud] = value.template; + }); + } else if (res.data.endpoints.type === "http-operation") { + clientConfig.endpointResource = res.data.endpoints.endpointResource; + } + + return clientConfig; + } catch (err: any) { + // catch 404 error + if (err.response?.status === 404) { + return null; + } + throw err; + } + } + + /** + * Update workspace client config + */ + static async updateClientConfig(workspaceUrl: string, config: any): Promise { + await axios.post(`${workspaceUrl}/ClientConfig`, config); + } + + /** + * Verify client config compatibility + */ + static async verifyClientConfig(workspaceUrl: string): Promise { + const url = `${workspaceUrl}/ClientConfig/AAZ/Compare`; + await axios.post(url); + } + + /** + * Inherit client config from AAZ + */ + static async inheritClientConfig(workspaceUrl: string): Promise { + const url = `${workspaceUrl}/ClientConfig/AAZ/Inherit`; + await axios.post(url); + } + + /** + * Generate/export workspace + */ + static async generateWorkspace(workspaceUrl: string): Promise { + const url = `${workspaceUrl}/Generate`; + await axios.post(url); + } + + /** + * Get workspace resources for reloading + */ + static async getWorkspaceResources(workspaceUrl: string): Promise { + const res = await axios.get(`${workspaceUrl}/CommandTree/Nodes/aaz/Resources`); + return res.data; + } + + /** + * Get workspace swagger default settings + */ + static async getWorkspaceSwaggerDefault(workspaceName: string): Promise { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); + return res.data; + } + + /** + * Reload swagger resources + */ + static async reloadSwaggerResources(workspaceUrl: string, data: any): Promise { + const reloadUrl = `${workspaceUrl}/Resources/ReloadSwagger`; + await axios.post(reloadUrl, data); + } + + /** + * Reload TypeSpec resources + */ + static async reloadTypespecResources(workspaceUrl: string, data: any): Promise { + const reloadUrl = `${workspaceUrl}/Resources/ReloadTypespec`; + await axios.post(reloadUrl, data); + } + + /** + * Get workspace swagger default settings + */ + static async getSwaggerDefault(workspaceName: string): Promise { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); + return res.data; + } + + /** + * Get workspace resources by workspace name (for swagger picker) + */ + static async getWorkspaceResourcesByName(workspaceName: string): Promise { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/Resources`); + return res.data; + } + + /** + * Add swagger resources to workspace + */ + static async addSwaggerResources(workspaceName: string, requestBody: any): Promise { + await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddSwagger`, requestBody); + } + + /** + * Add TypeSpec resources to workspace + */ + static async addTypespecResources(workspaceName: string, requestBody: any): Promise { + await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, requestBody); + } +} diff --git a/src/web/src/views/workspace/WSEditor.tsx b/src/web/src/views/workspace/WSEditor.tsx index 132170ff..1da0d97d 100644 --- a/src/web/src/views/workspace/WSEditor.tsx +++ b/src/web/src/views/workspace/WSEditor.tsx @@ -22,7 +22,6 @@ import { Alert, } from "@mui/material"; import { useParams } from "react-router"; -import axios from "axios"; import { TransitionProps } from "@mui/material/transitions"; import WSEditorSwaggerPicker from "./WSEditorSwaggerPicker"; import WSEditorToolBar from "./WSEditorToolBar"; @@ -39,8 +38,9 @@ import WSEditorCommandContent, { DecodeResponseCommand, ResponseCommand, } from "./WSEditorCommandContent"; -import WSEditorClientConfigDialog, { ClientConfig } from "./WSEditorClientConfig"; +import WSEditorClientConfigDialog from "./WSEditorClientConfig"; import { getTypespecRPResourcesOperations } from "../../typespec"; +import { WorkspaceApiService, SpecsApiService, ApiErrorHandler } from "../../services"; interface CommandGroupMap { [id: string]: CommandGroup; @@ -123,11 +123,8 @@ class WSEditor extends React.Component { } try { - let res = await axios.get(`/AAZ/Specs/Planes`); - const planeNames: string[] = res.data.map((v: any) => { - return v.name; - }); - res = await axios.get(workspaceUrl); + const planeNames = await SpecsApiService.getPlaneNames(); + const workspaceData = await WorkspaceApiService.getWorkspace(workspaceUrl); const reloadTimestamp = Date.now(); const commandMap: CommandMap = {}; const commandGroupMap: CommandGroupMap = {}; @@ -186,8 +183,8 @@ class WSEditor extends React.Component { const commandTree: CommandTreeNode[] = []; - if (res.data.commandTree.commandGroups) { - const cmdGroups: ResponseCommandGroups = res.data.commandTree.commandGroups; + if (workspaceData.commandTree.commandGroups) { + const cmdGroups: ResponseCommandGroups = workspaceData.commandTree.commandGroups; for (const key in cmdGroups) { commandTree.push(buildCommandGroup(cmdGroups[key])); } @@ -230,7 +227,7 @@ class WSEditor extends React.Component { } // when the plane name not included in the built-in planes, it is a client configurable plane - const clientConfigurable = !planeNames.includes(res.data.plane); + const clientConfigurable = !planeNames.includes(workspaceData.plane); this.setState((preState) => { const newExpanded = new Set(); @@ -250,8 +247,8 @@ class WSEditor extends React.Component { return { ...preState, - plane: res.data.plane, - source: res.data.source, + plane: workspaceData.plane, + source: workspaceData.source, clientConfigurable: clientConfigurable, commandTree: commandTree, selected: selected, @@ -297,30 +294,7 @@ class WSEditor extends React.Component { }; getWorkspaceClientConfig = async (workspaceUrl: string) => { - try { - const res = await axios.get(`${workspaceUrl}/ClientConfig`); - const clientConfig: ClientConfig = { - version: res.data.version, - endpointTemplates: undefined, - endpointResource: undefined, - auth: res.data.auth, - }; - if (res.data.endpoints.type === "template") { - clientConfig.endpointTemplates = {}; - res.data.endpoints.templates.forEach((value: any) => { - clientConfig.endpointTemplates![value.cloud] = value.template; - }); - } else if (res.data.endpoints.type === "http-operation") { - clientConfig.endpointResource = res.data.endpoints.endpointResource; - } - - return clientConfig; - } catch (err: any) { - // catch 404 error - if (err.response?.status === 404) { - return null; - } - } + return await WorkspaceApiService.getWorkspaceClientConfig(workspaceUrl); }; showClientConfigDialog = () => { @@ -623,14 +597,13 @@ class WSEditorExportDialog extends React.Component { - const url = `${this.props.workspaceUrl}/ClientConfig/AAZ/Compare`; this.setState({ updating: true }); try { - await axios.post(url); + await WorkspaceApiService.verifyClientConfig(this.props.workspaceUrl); this.setState({ clientConfigOOD: false, updating: false }); } catch (err: any) { // catch 409 error - if (err.response?.status === 409) { + if (ApiErrorHandler.isHttpError(err, 409)) { this.setState({ invalidText: `The client config in this workspace is out of date. Please refresh it first.`, clientConfigOOD: true, @@ -639,53 +612,42 @@ class WSEditorExportDialog extends React.Component { - const url = `${this.props.workspaceUrl}/ClientConfig/AAZ/Inherit`; this.setState({ updating: true }); try { - await axios.post(url); + await WorkspaceApiService.inheritClientConfig(this.props.workspaceUrl); this.setState({ clientConfigOOD: false, updating: false }); this.props.onClose(false, true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } - this.setState({ updating: false }); + this.setState({ + invalidText: ApiErrorHandler.getErrorMessage(err), + updating: false, + }); } }; handleExport = async () => { - const url = `${this.props.workspaceUrl}/Generate`; this.setState({ updating: true }); try { - await axios.post(url); + await WorkspaceApiService.generateWorkspace(this.props.workspaceUrl); this.setState({ updating: false }); this.props.onClose(false, false); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } - this.setState({ updating: false }); + this.setState({ + invalidText: ApiErrorHandler.getErrorMessage(err), + updating: false, + }); } }; @@ -732,19 +694,14 @@ function WSEditorDeleteDialog(props: { workspaceName: string; open: boolean; onC const handleDelete = () => { setUpdating(true); - const nodeUrl = `/AAZ/Editor/Workspaces/` + props.workspaceName; - axios - .delete(nodeUrl) + WorkspaceApiService.deleteWorkspace(props.workspaceName) .then(() => { setUpdating(false); props.onClose(true); }) - .catch((err) => { - console.error(err.response.data); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } + .catch((err: any) => { + console.error(err); + setInvalidText(ApiErrorHandler.getErrorMessage(err)); setUpdating(false); }); }; @@ -832,8 +789,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< updating: true, }); try { - const res = await axios.get(`${this.props.workspaceUrl}/CommandTree/Nodes/aaz/Resources`); - const resources: Resource[] = res.data; + const resources: Resource[] = await WorkspaceApiService.getWorkspaceResources(this.props.workspaceUrl); this.setState({ updating: false, resourceOptions: resources, @@ -841,13 +797,10 @@ class WSEditorSwaggerReloadDialog extends React.Component< }); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - updating: false, - }); - } + this.setState({ + invalidText: ApiErrorHandler.getErrorMessage(err), + updating: false, + }); } }; @@ -878,15 +831,10 @@ class WSEditorSwaggerReloadDialog extends React.Component< updating: true, }); - const reloadUrl = - this.props.source.toLowerCase() === "typespec" - ? `${this.props.workspaceUrl}/Resources/ReloadTypespec` - : `${this.props.workspaceUrl}/Resources/ReloadSwagger`; - try { if (this.props.source.toLowerCase() === "typespec") { - const res = await axios.get(`/AAZ/Editor/Workspaces/${this.props.workspaceName}/SwaggerDefault`); - const { modNames, rpName, source } = res.data; + const swaggerDefault = await WorkspaceApiService.getWorkspaceSwaggerDefault(this.props.workspaceName); + const { modNames, rpName, source } = swaggerDefault; if (!modNames || modNames.length === 0 || !rpName || !source || source.toLowerCase() !== "typespec") { this.setState({ invalidText: "Invalid workspace info", @@ -896,10 +844,10 @@ class WSEditorSwaggerReloadDialog extends React.Component< } const resourceProviderUrl = "/Swagger/Specs/" + - res.data.plane + + swaggerDefault.plane + "/" + - res.data.modNames.join("/") + - `/ResourceProviders/${res.data.rpName}/TypeSpec`; + swaggerDefault.modNames.join("/") + + `/ResourceProviders/${swaggerDefault.rpName}/TypeSpec`; const requestBody = { version: resourceOptions[0].version, resources: data.resources, @@ -916,22 +864,21 @@ class WSEditorSwaggerReloadDialog extends React.Component< return; } data.resources = emitterOptionRes; - // console.log("reload typespec data: ", data); + await WorkspaceApiService.reloadTypespecResources(this.props.workspaceUrl, data); + } else { + await WorkspaceApiService.reloadSwaggerResources(this.props.workspaceUrl, data); } - await axios.post(reloadUrl, data); + this.setState({ updating: false, }); this.props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - updating: false, - }); - } + this.setState({ + invalidText: ApiErrorHandler.getErrorMessage(err), + updating: false, + }); } }; @@ -1133,26 +1080,18 @@ class WSRenameDialog extends React.Component { + WorkspaceApiService.renameWorkspace(workspaceUrl, nName) + .then((res: any) => { this.setState({ updating: false, }); - this.props.onClose(res.data.name); + this.props.onClose(res.name); }) - .catch((err) => { + .catch((err: any) => { this.setState({ updating: false, + invalidText: ApiErrorHandler.getErrorMessage(err), }); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } }); } }; diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 02997f3c..feaf6d77 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -29,7 +29,6 @@ import { ButtonBase, FormLabelProps, } from "@mui/material"; -import axios from "axios"; import React, { useState, useEffect } from "react"; import MuiAccordionSummary from "@mui/material/AccordionSummary"; import { @@ -48,6 +47,7 @@ import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; import DataObjectIcon from "@mui/icons-material/DataObject"; import LabelIcon from "@mui/icons-material/Label"; +import { CommandApiService, ApiErrorHandler } from "../../services"; import WSEditorCommandArgumentsContent, { ClsArgDefinitionMap, CMDArg, @@ -268,8 +268,8 @@ class WSEditorCommandContent extends React.Component { setRelatedCommands([]); const urls = getUrls(); - const promisesAll = urls.map((url) => { - return axios.get(`${url}/Commands`); + const promisesAll = urls.map(async (url) => { + return await CommandApiService.getCommandsForResource(url); }); Promise.all(promisesAll) .then((responses) => { const commands = new Set(); - responses.forEach((response: any) => { - const responseCommands: ResponseCommand[] = response.data; + responses.forEach((responseCommands: ResponseCommand[]) => { responseCommands .map((responseCommand) => DecodeResponseCommand(responseCommand)) .forEach((cmd) => { @@ -813,8 +812,8 @@ function CommandDeleteDialog(props: { const handleDelete = () => { setUpdating(true); const urls = getUrls(); - const promisesAll = urls.map((url) => { - return axios.delete(url); + const promisesAll = urls.map(async (url) => { + return await CommandApiService.deleteResource(url); }); Promise.all(promisesAll) .then(() => { @@ -881,7 +880,7 @@ class CommandDialog extends React.Component { + handleModify = async () => { let { name, shortHelp, longHelp, confirmation } = this.state; const { stage } = this.state; @@ -937,50 +936,39 @@ class CommandDialog extends React.Component { - const name = names.join(" "); - if (name === command.names.join(" ")) { - const cmd = DecodeResponseCommand(res.data); - this.setState({ - updating: false, - }); - this.props.onClose(cmd); - } else { - // Rename command - axios - .post(`${leafUrl}/Rename`, { - name: name, - }) - .then((res) => { - const cmd = DecodeResponseCommand(res.data); - this.setState({ - updating: false, - }); - this.props.onClose(cmd); - }); - } - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } + }); + + const name = names.join(" "); + if (name === command.names.join(" ")) { + const cmd = DecodeResponseCommand(commandData); this.setState({ updating: false, }); + this.props.onClose(cmd); + } else { + // Rename command + const renamedData = await CommandApiService.renameCommand(leafUrl, name); + const cmd = DecodeResponseCommand(renamedData); + this.setState({ + updating: false, + }); + this.props.onClose(cmd); + } + } catch (err: any) { + console.error(err); + this.setState({ + invalidText: ApiErrorHandler.getErrorMessage(err), + updating: false, }); + } }; handleClose = () => { @@ -1157,7 +1145,7 @@ class ExampleDialog extends React.Component { + onUpdateExamples = async (examples: Example[]) => { const { workspaceUrl, command } = this.props; const leafUrl = @@ -1169,29 +1157,22 @@ class ExampleDialog extends React.Component { - const cmd = DecodeResponseCommand(res.data); - this.setState({ - updating: false, - }); - this.props.onClose(cmd); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } - this.setState({ - updating: false, - }); + + try { + const responseData = await CommandApiService.updateCommandExamples(leafUrl, examples); + const cmd = DecodeResponseCommand(responseData); + this.setState({ + updating: false, }); + this.props.onClose(cmd); + } catch (err: any) { + console.error(err); + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + updating: false, + }); + } }; handleDelete = () => { @@ -1338,16 +1319,7 @@ class ExampleDialog extends React.Component { - return { - name: v.name, - commands: v.commands, - }; - }); + const examples = await CommandApiService.generateSwaggerExamples(leafUrl); this.setState({ exampleOptions: examples, updating: false, @@ -1623,17 +1595,15 @@ function AddSubcommandDialog(props: { setUpdating(true); try { - await axios.post(urls[0], { + await CommandApiService.createSubresource(urls[0], { ...data, arg: props.argVar, }); props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } + const message = ApiErrorHandler.getErrorMessage(err); + setInvalidText(`ResponseError: ${message}`); setUpdating(false); } }; @@ -1901,7 +1871,7 @@ function OutputDialog(props: { props.onClose(); }; - const handleUpdateOutput = () => { + const handleUpdateOutput = async () => { setInvalidText(undefined); setUpdating(true); @@ -1918,23 +1888,17 @@ function OutputDialog(props: { console.log("New clientFlatten: "); console.log(output.clientFlatten); - axios - .patch(leafUrl, { - outputs: outputs, - }) - .then((res) => { - const cmd = DecodeResponseCommand(res.data); - setUpdating(false); - props.onClose(cmd); - }) - .catch((err) => { - console.error(err.response); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } - setUpdating(false); - }); + try { + const responseData = await CommandApiService.updateCommandOutputs(leafUrl, outputs); + const cmd = DecodeResponseCommand(responseData); + setUpdating(false); + props.onClose(cmd); + } catch (err: any) { + console.error(err); + const message = ApiErrorHandler.getErrorMessage(err); + setInvalidText(`ResponseError: ${message}`); + setUpdating(false); + } } else { console.error(`Invalid output type for flatten switch: ${output.type}`); setInvalidText(`Invalid output type for flatten switch: ${output.type}`); diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index 4584e99c..07f70ee1 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -27,7 +27,7 @@ import { FormHelperText, } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; -import axios from "axios"; +import { WorkspaceApiService, SpecsApiService, ApiErrorHandler } from "../../services"; import EditorPageLayout from "../../components/EditorPageLayout"; import { styled } from "@mui/material/styles"; import { getTypespecRPResources, getTypespecRPResourcesOperations } from "../../typespec"; @@ -158,37 +158,35 @@ class WSEditorSwaggerPicker extends React.Component { await this.loadSwaggerModules(this.props.plane); try { - const res = await axios.get(`/AAZ/Editor/Workspaces/${this.props.workspaceName}/SwaggerDefault`); + const swaggerDefault = await WorkspaceApiService.getSwaggerDefault(this.props.workspaceName); // default module name - if (res.data.modNames === null || res.data.modNames.length == 0) { + if (swaggerDefault.modNames === null || swaggerDefault.modNames.length == 0) { return; } - const moduleValueUrl = `/Swagger/Specs/${this.props.plane}/` + res.data.modNames.join("/"); + const moduleValueUrl = `/Swagger/Specs/${this.props.plane}/` + swaggerDefault.modNames.join("/"); if (this.state.moduleOptions.findIndex((v) => v === moduleValueUrl) == -1) { return; } let rpUrl = null; - if (res.data.rpName !== null && res.data.rpName.length > 0) { - rpUrl = `${moduleValueUrl}/ResourceProviders/${res.data.rpName}`; - if (res.data.source === "TypeSpec") { + if (swaggerDefault.rpName !== null && swaggerDefault.rpName.length > 0) { + rpUrl = `${moduleValueUrl}/ResourceProviders/${swaggerDefault.rpName}`; + if (swaggerDefault.source === "TypeSpec") { rpUrl += `/TypeSpec`; } } this.setState({ defaultModule: moduleValueUrl, - defaultSource: res.data.source, + defaultSource: swaggerDefault.source, selectedModule: moduleValueUrl, moduleOptions: [moduleValueUrl], // only the default module selectable. }); await this.loadResourceProviders(moduleValueUrl, rpUrl); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); } }); } @@ -203,8 +201,7 @@ class WSEditorSwaggerPicker extends React.Component { try { - const res = await axios.get(`/Swagger/Specs/${plane}`); - const options: string[] = res.data.map((v: any) => v.url); + const options = await SpecsApiService.getSwaggerModules(plane); this.setState((preState) => { return { ...preState, @@ -215,12 +212,10 @@ class WSEditorSwaggerPicker extends React.Component v.url); + let options = await SpecsApiService.getResourceProvidersWithType(moduleUrl, defaultSource ?? undefined); let selectedResourceProvider = options.length === 1 ? options[0] : null; let defaultResourceProvider = null; if (preferredRP !== null && options.findIndex((v) => v === preferredRP) >= 0) { @@ -249,12 +239,10 @@ class WSEditorSwaggerPicker extends React.Component { try { - const res = await axios.get(`/AAZ/Editor/Workspaces/${this.props.workspaceName}/CommandTree/Nodes/aaz/Resources`); + const resources = await WorkspaceApiService.getWorkspaceResourcesByName(this.props.workspaceName); const existingResources = new Set(); - if (res.data && Array.isArray(res.data) && res.data.length > 0) { - res.data.forEach((resource) => { + if (resources && Array.isArray(resources) && resources.length > 0) { + resources.forEach((resource: any) => { existingResources.add(resource.id); }); } @@ -278,12 +266,10 @@ class WSEditorSwaggerPicker extends React.Component { + const filterData = await SpecsApiService.filterResourcesByPlane(this.props.plane, resourceIdList); + filterData.resources.forEach((aazResource: AAZResource) => { if (aazResource.versions) { resourceMap[aazResource.id].aazVersions = aazResource.versions; } @@ -378,7 +357,7 @@ class WSEditorSwaggerPicker extends React.Component { + addSwagger = async () => { const { selectedResources, selectedVersion, @@ -451,75 +430,61 @@ class WSEditorSwaggerPicker extends React.Component { - console.log("emitter getTypespecRPResourceOperations res: ", res); - console.log("resourceOptionMap: ", resourceOptionMap); - const addTypespecData = { - version: selectedVersion, - resources: res.map((item: { id: string; [key: string]: any }) => { - if (item.id in resourceOptionMap) { - item.options = resourceOptionMap[item.id]; - } - return item; - }), - }; - console.log("addTypespec data: ", addTypespecData); - axios - .post( - `/AAZ/Editor/Workspaces/${this.props.workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, - addTypespecData, - ) - .then(() => { - this.setState({ - loading: false, - }); - this.props.onClose(true); - }) - .catch((err) => { - console.error(err); - this.setState({ - loading: false, - }); - this.props.onClose(false); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - }); - } - }); - }) - .catch((err: any) => { + try { + const res = await getTypespecRPResourcesOperations(requestEmitterObj); + console.log("emitter getTypespecRPResourceOperations res: ", res); + console.log("resourceOptionMap: ", resourceOptionMap); + const addTypespecData = { + version: selectedVersion, + resources: res.map((item: { id: string; [key: string]: any }) => { + if (item.id in resourceOptionMap) { + item.options = resourceOptionMap[item.id]; + } + return item; + }), + }; + console.log("addTypespec data: ", addTypespecData); + try { + await WorkspaceApiService.addTypespecResources(this.props.workspaceName, addTypespecData); this.setState({ loading: false, }); this.props.onClose(true); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - }); - } - }); - } else { - axios - .post(`/AAZ/Editor/Workspaces/${this.props.workspaceName}/CommandTree/Nodes/aaz/AddSwagger`, requestBody) - .then(() => { + } catch (err: any) { + console.error(err); this.setState({ loading: false, }); - this.props.onClose(true); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - }); - } + this.props.onClose(false); + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); + } + } catch (err: any) { + this.setState({ + loading: false, + }); + this.props.onClose(true); + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); + } + } else { + try { + await WorkspaceApiService.addSwaggerResources(this.props.workspaceName, requestBody); + this.setState({ + loading: false, + }); + this.props.onClose(true); + } catch (err: any) { + console.error(err); + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, }); + } } }; diff --git a/src/web/src/views/workspace/WorkspaceSelector.tsx b/src/web/src/views/workspace/WorkspaceSelector.tsx index 6bb3417a..fe6902c9 100644 --- a/src/web/src/views/workspace/WorkspaceSelector.tsx +++ b/src/web/src/views/workspace/WorkspaceSelector.tsx @@ -11,22 +11,11 @@ import { InputLabel, Alert, } from "@mui/material"; -import axios from "axios"; import * as React from "react"; -import { Url } from "url"; import { SwaggerItemSelector } from "./WSEditorSwaggerPicker"; import styled from "@emotion/styled"; import { Plane } from "./WSEditorCommandContent"; - -interface Workspace { - name: string; - plane: string | null; - modNames: string | null; - resourceProvider: string | null; - lastModified: Date | null; - url: Url | null; - folder: string | null; -} +import { WorkspaceApiService, SpecsApiService, ApiErrorHandler, type Workspace as WorkspaceType } from "../../services"; interface WorkspaceSelectorProps { name: string; @@ -34,7 +23,7 @@ interface WorkspaceSelectorProps { interface WorkspaceSelectorState { options: any[]; - value: Workspace | null; + value: WorkspaceType | null; openDialog: boolean; newWorkspaceName: string; } @@ -44,7 +33,7 @@ interface InputType { title: string; } -const filter = createFilterOptions(); +const filter = createFilterOptions(); class WorkspaceSelector extends React.Component { constructor(props: WorkspaceSelectorProps) { @@ -63,16 +52,7 @@ class WorkspaceSelector extends React.Component { try { - const res = await axios.get("/AAZ/Editor/Workspaces"); - const options = res.data.map((option: any) => { - return { - name: option.name, - lastModified: new Date(option.updated * 1000), - url: option.url, - plane: option.plane, - folder: option.folder, - }; - }); + const options = await WorkspaceApiService.getWorkspaces(); this.setState({ options: options, }); @@ -236,15 +216,8 @@ class WorkspaceCreateDialog extends React.Component { - return { - name: v.name, - displayName: v.displayName, - moduleOptions: undefined, - }; - }); - const planeOptions: string[] = res.data.map((v: any) => v.displayName); + const planes = await SpecsApiService.getPlanes(); + const planeOptions: string[] = planes.map((v) => v.displayName); this.setState({ planes: planes, planeOptions: planeOptions, @@ -253,13 +226,10 @@ class WorkspaceCreateDialog extends React.Component v.url); + const options = await SpecsApiService.getModulesForPlane(plane!.name); this.setState((preState) => { const planes = preState.planes; const index = planes.findIndex((v) => v.name === plane!.name); @@ -310,13 +279,10 @@ class WorkspaceCreateDialog extends React.Component v.url); + const options = await SpecsApiService.getResourceProviders(moduleUrl); const selectedResourceProvider = options.length === 1 ? options[0] : null; this.setState({ loading: false, @@ -358,13 +323,10 @@ class WorkspaceCreateDialog extends React.Component Date: Mon, 29 Sep 2025 13:08:24 +1000 Subject: [PATCH 02/18] refactor: api usage in WSEditorClientConfig --- src/web/src/services/workspaceApi.ts | 8 ++ .../views/workspace/WSEditorClientConfig.tsx | 101 +++++++----------- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts index d4104922..087b5310 100644 --- a/src/web/src/services/workspaceApi.ts +++ b/src/web/src/services/workspaceApi.ts @@ -209,4 +209,12 @@ export class WorkspaceApiService { static async addTypespecResources(workspaceName: string, requestBody: any): Promise { await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, requestBody); } + + /** + * Get workspace client configuration + */ + static async getClientConfig(workspaceUrl: string): Promise { + const res = await axios.get(`${workspaceUrl}/ClientConfig`); + return res.data; + } } diff --git a/src/web/src/views/workspace/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index 1c31dd66..f8b7757b 100644 --- a/src/web/src/views/workspace/WSEditorClientConfig.tsx +++ b/src/web/src/views/workspace/WSEditorClientConfig.tsx @@ -19,7 +19,7 @@ import { Tabs, Tab, } from "@mui/material"; -import axios from "axios"; +import { WorkspaceApiService, SpecsApiService, ApiErrorHandler } from "../../services"; import DoDisturbOnRoundedIcon from "@mui/icons-material/DoDisturbOnRounded"; import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import { Plane, Resource } from "./WSEditorCommandContent"; @@ -163,15 +163,8 @@ class WSEditorClientConfigDialog extends React.Component< updating: true, }); - const res = await axios.get(`/AAZ/Specs/Planes`); - const planes: Plane[] = res.data.map((v: any) => { - return { - name: v.name, - displayName: v.displayName, - moduleOptions: undefined, - }; - }); - const planeOptions: string[] = res.data.map((v: any) => v.displayName); + const planes = await SpecsApiService.getPlanes(); + const planeOptions: string[] = planes.map((v: any) => v.displayName); this.setState({ planes: planes, planeOptions: planeOptions, @@ -180,13 +173,11 @@ class WSEditorClientConfigDialog extends React.Component< await this.onPlaneSelectorUpdate(planeOptions[0]); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - updating: false, - invalidText: `ResponseError: ${data.message!}`, - }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + updating: false, + invalidText: `ResponseError: ${message}`, + }); } }; @@ -220,8 +211,7 @@ class WSEditorClientConfigDialog extends React.Component< this.setState({ updating: true, }); - const res = await axios.get(`/Swagger/Specs/${plane!.name}`); - const options: string[] = res.data.map((v: any) => v.url); + const options = await SpecsApiService.getSwaggerModules(plane!.name); this.setState((preState) => { const planes = preState.planes; const index = planes.findIndex((v) => v.name === plane!.name); @@ -237,13 +227,11 @@ class WSEditorClientConfigDialog extends React.Component< await this.onModuleSelectionUpdate(null); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - updating: false, - invalidText: `ResponseError: ${data.message!}`, - }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + updating: false, + invalidText: `ResponseError: ${message}`, + }); } } } else { @@ -274,8 +262,7 @@ class WSEditorClientConfigDialog extends React.Component< this.setState({ updating: true, }); - const res = await axios.get(`${moduleUrl}/ResourceProviders`); - const options: string[] = res.data.map((v: any) => v.url); + const options = await SpecsApiService.getResourceProviders(moduleUrl); const selectedResourceProvider = options.length === 1 ? options[0] : null; this.setState({ updating: false, @@ -285,13 +272,11 @@ class WSEditorClientConfigDialog extends React.Component< this.onResourceProviderUpdate(selectedResourceProvider); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - updating: false, - invalidText: `ResponseError: ${data.message!}`, - }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + updating: false, + invalidText: `ResponseError: ${message}`, + }); } } else { this.setState({ @@ -322,11 +307,11 @@ class WSEditorClientConfigDialog extends React.Component< updating: true, }); try { - const res = await axios.get(`${resourceProviderUrl}/Resources`); + const resources = await SpecsApiService.getProviderResources(resourceProviderUrl); const versionResourceIdMap: SwaggerVersionResourceIdMap = {}; const versionOptions: string[] = []; const resourceIdList: string[] = []; - res.data.forEach((resource: any) => { + resources.forEach((resource: any) => { resourceIdList.push(resource.id); const resourceVersions = resource.versions .filter((v: ResourceVersion) => { @@ -365,12 +350,10 @@ class WSEditorClientConfigDialog extends React.Component< this.onVersionUpdate(selectVersion); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}`, - }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); } } else { this.setState({ @@ -405,10 +388,10 @@ class WSEditorClientConfigDialog extends React.Component< loadWorkspaceClientConfig = async () => { this.setState({ updating: true }); try { - const res = await axios.get(`${this.props.workspaceUrl}/ClientConfig`); + const clientConfigData = await WorkspaceApiService.getClientConfig(this.props.workspaceUrl); const clientConfig: ClientConfig = { - version: res.data.version, - auth: res.data.auth, + version: clientConfigData.version, + auth: clientConfigData.auth, }; let templateAzureCloud = ""; let templateAzureChinaCloud = ""; @@ -424,12 +407,12 @@ class WSEditorClientConfigDialog extends React.Component< let selectedResourceId: string | null = null; let subresource: string = ""; - if (res.data.endpoints.type === "template") { + if (clientConfigData.endpoints.type === "template") { clientConfig.endpointTemplates = {}; - res.data.endpoints.templates.forEach((value: any) => { + clientConfigData.endpoints.templates.forEach((value: any) => { clientConfig.endpointTemplates![value.cloud] = value.template; }); - clientConfig.endpointCloudMetadata = res.data.endpoints.cloudMetadata; + clientConfig.endpointCloudMetadata = clientConfigData.endpoints.cloudMetadata; endpointType = "template"; templateAzureCloud = clientConfig.endpointTemplates!["AzureCloud"] ?? ""; @@ -438,8 +421,8 @@ class WSEditorClientConfigDialog extends React.Component< templateAzureGermanCloud = clientConfig.endpointTemplates!["AzureGermanCloud"] ?? ""; cloudMetadataSelectorIndex = clientConfig.endpointCloudMetadata?.selectorIndex ?? ""; cloudMetadataPrefixTemplate = clientConfig.endpointCloudMetadata?.prefixTemplate ?? ""; - } else if (res.data.endpoints.type === "http-operation") { - clientConfig.endpointResource = res.data.endpoints.resource; + } else if (clientConfigData.endpoints.type === "http-operation") { + clientConfig.endpointResource = clientConfigData.endpoints.resource; const rpUrl: string = clientConfig.endpointResource!.swagger.split("/Paths/")[0]; const moduleUrl: string = rpUrl.split("/ResourceProviders/")[0]; const planeUrl: string = moduleUrl.split("/")[0]; @@ -471,16 +454,14 @@ class WSEditorClientConfigDialog extends React.Component< }); } catch (err: any) { // catch 404 error - if (err.response?.status === 404) { + if (ApiErrorHandler.isHttpError(err, 404)) { this.setState({ isAdd: true, }); } else { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}` }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ invalidText: `ResponseError: ${message}` }); } } @@ -657,7 +638,7 @@ class WSEditorClientConfigDialog extends React.Component< ) => { this.setState({ updating: true }); try { - await axios.post(`${this.props.workspaceUrl}/ClientConfig`, { + await WorkspaceApiService.updateClientConfig(this.props.workspaceUrl, { templates: templates, cloudMetadata: cloudMetadata, resource: resource, @@ -667,10 +648,8 @@ class WSEditorClientConfigDialog extends React.Component< this.props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}` }); - } + const message = ApiErrorHandler.getErrorMessage(err); + this.setState({ invalidText: `ResponseError: ${message}` }); this.setState({ updating: false }); } }; From 0ddcb15db62f9349058641197ef05f9857b2390e Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 13:19:09 +1000 Subject: [PATCH 03/18] refactor: update WSEditorCommandArgumentsContent with new API pattern --- src/web/src/services/commandApi.ts | 9 ++ .../WSEditorCommandArgumentsContent.tsx | 109 ++++++------------ 2 files changed, 46 insertions(+), 72 deletions(-) diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index 41208d05..ec79a9d8 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -109,6 +109,15 @@ export class CommandApiService { await axios.post(flattenUrl); } + /** + * Find similar arguments for a command argument + */ + static async findSimilarArguments(commandUrl: string, argVar: string): Promise { + const similarUrl = `${commandUrl}/Arguments/${argVar}/FindSimilar`; + const res = await axios.post(similarUrl); + return res.data; + } + /** * Create a subresource for a command resource */ diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index f1e967e2..a5dbef79 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -27,7 +27,7 @@ import CallSplitSharpIcon from "@mui/icons-material/CallSplitSharp"; import EditIcon from "@mui/icons-material/Edit"; import ImportExportIcon from "@mui/icons-material/ImportExport"; -import axios from "axios"; +import { CommandApiService, ApiErrorHandler } from "../../services"; import pluralize from "pluralize"; import React, { useEffect, useState } from "react"; import WSECArgumentSimilarPicker, { ArgSimilarTree, BuildArgSimilarTree } from "./argument/WSECArgumentSimilarPicker"; @@ -920,46 +920,35 @@ function ArgumentDialog(props: { const argumentUrl = `${props.commandUrl}/Arguments/${props.arg.var}`; try { - await axios.patch(argumentUrl, { - ...data, - }); + await CommandApiService.updateCommandArgument(argumentUrl, data); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } + setInvalidText(ApiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; - const handleDisplaySimilar = () => { + const handleDisplaySimilar = async () => { if (verifyModification() === undefined) { return; } setUpdating(true); - const similarUrl = `${props.commandUrl}/Arguments/${props.arg.var}/FindSimilar`; - axios - .post(similarUrl) - .then((res) => { - setUpdating(false); - const { tree, expandedIds } = BuildArgSimilarTree(res); - setArgSimilarTree(tree); - setArgSimilarTreeExpandedIds(expandedIds); - setArgSimilarTreeArgIdsUpdated([]); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } - setUpdating(false); - }); + try { + const res = await CommandApiService.findSimilarArguments(props.commandUrl, props.arg.var); + setUpdating(false); + const { tree, expandedIds } = BuildArgSimilarTree(res); + setArgSimilarTree(tree); + setArgSimilarTreeExpandedIds(expandedIds); + setArgSimilarTreeArgIdsUpdated([]); + } catch (err: any) { + console.error(err); + setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setUpdating(false); + } }; const handleDisableSimilar = () => { @@ -988,17 +977,12 @@ function ArgumentDialog(props: { const argId = argSimilarTree!.selectedArgIds[idx]; if (updatedIds.indexOf(argId) === -1) { try { - await axios.patch(argId, { - ...data, - }); + await CommandApiService.updateArgumentById(argId, data); updatedIds.push(argId); setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - invalidText += `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`; - } + invalidText += ApiErrorHandler.getErrorMessage(err); } } } @@ -1469,46 +1453,35 @@ function FlattenDialog(props: { const flattenUrl = `${props.commandUrl}/Arguments/${props.arg.var}/Flatten`; try { - await axios.post(flattenUrl, { - ...data, - }); + await CommandApiService.flattenArgument(flattenUrl, data); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } + setInvalidText(ApiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; - const handleDisplaySimilar = () => { + const handleDisplaySimilar = async () => { if (verifyFlatten() === undefined) { return; } setUpdating(true); - const similarUrl = `${props.commandUrl}/Arguments/${props.arg.var}/FindSimilar`; - axios - .post(similarUrl) - .then((res) => { - setUpdating(false); - const { tree, expandedIds } = BuildArgSimilarTree(res); - setArgSimilarTree(tree); - setArgSimilarTreeExpandedIds(expandedIds); - setArgSimilarTreeArgIdsUpdated([]); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } - setUpdating(false); - }); + try { + const res = await CommandApiService.findSimilarArguments(props.commandUrl, props.arg.var); + setUpdating(false); + const { tree, expandedIds } = BuildArgSimilarTree(res); + setArgSimilarTree(tree); + setArgSimilarTreeExpandedIds(expandedIds); + setArgSimilarTreeArgIdsUpdated([]); + } catch (err: any) { + console.error(err); + setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setUpdating(false); + } }; const handleDisableSimilar = () => { @@ -1537,17 +1510,12 @@ function FlattenDialog(props: { if (updatedIds.indexOf(argId) === -1) { const flattenUrl = `${argId}/Flatten`; try { - await axios.post(flattenUrl, { - ...data, - }); + await CommandApiService.flattenArgument(flattenUrl, data); updatedIds.push(argId); setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - invalidText += `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`; - } + invalidText += ApiErrorHandler.getErrorMessage(err); } } } @@ -1686,15 +1654,12 @@ function UnwrapClsDialog(props: { const flattenUrl = `${props.commandUrl}/Arguments/${argVar}/UnwrapClass`; try { - await axios.post(flattenUrl); + await CommandApiService.unwrapClassArgument(flattenUrl); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } + setInvalidText(ApiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; From f1c8492983f622267be083a90617884e4772d10c Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 13:49:00 +1000 Subject: [PATCH 04/18] refactor: standardize api call patterns to async/await --- src/web/src/services/commandApi.ts | 26 ++++++ .../workspace/WSEditorCommandContent.tsx | 11 +-- .../workspace/WSEditorCommandGroupContent.tsx | 83 ++++++++----------- .../views/workspace/WSEditorSwaggerPicker.tsx | 9 +- 4 files changed, 68 insertions(+), 61 deletions(-) diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index ec79a9d8..a7ceb894 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -109,6 +109,32 @@ export class CommandApiService { await axios.post(flattenUrl); } + /** + * Delete command group/resource node + */ + static async deleteCommandGroup(nodeUrl: string): Promise { + await axios.delete(nodeUrl); + } + + /** + * Update command group properties + */ + static async updateCommandGroup( + nodeUrl: string, + data: { help: { short: string; lines: string[] }; stage: string }, + ): Promise { + const res = await axios.patch(nodeUrl, data); + return res.data; + } + + /** + * Rename command group + */ + static async renameCommandGroup(nodeUrl: string, name: string): Promise { + const res = await axios.post(`${nodeUrl}/Rename`, { name }); + return res.data; + } + /** * Find similar arguments for a command argument */ diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index feaf6d77..5042ee52 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -1329,13 +1329,10 @@ class ExampleDialog extends React.Component { props.onClose(false); }; - const handleDelete = () => { + const handleDelete = async () => { const nodeUrl = `${props.workspaceUrl}/CommandTree/Nodes/aaz/` + props.commandGroup.names.join("/"); setUpdating(true); - axios - .delete(nodeUrl) - .then(() => { - setUpdating(false); - props.onClose(true); - }) - .catch((err) => { - setUpdating(false); - console.error(err.response.data); - }); + try { + await CommandApiService.deleteCommandGroup(nodeUrl); + setUpdating(false); + props.onClose(true); + } catch (err: any) { + setUpdating(false); + console.error(err); + } }; return ( @@ -319,7 +317,7 @@ class CommandGroupDialog extends React.Component { + handleModify = async () => { let { name, shortHelp, longHelp } = this.state; const { stage } = this.state; const { workspaceUrl, commandGroup } = this.props; @@ -357,7 +355,7 @@ class CommandGroupDialog extends React.Component 1) { lines = longHelp.split("\n").filter((l) => l.length > 0); } @@ -368,49 +366,38 @@ class CommandGroupDialog extends React.Component { - const name = names.join(" "); - if (name === commandGroup.names.join(" ")) { - const cmdGroup = DecodeResponseCommandGroup(res.data); - this.setState({ - updating: false, - }); - this.props.onClose(cmdGroup); - } else { - // Rename command Group - axios - .post(`${nodeUrl}/Rename`, { - name: name, - }) - .then((res) => { - const cmdGroup = DecodeResponseCommandGroup(res.data); - this.setState({ - updating: false, - }); - this.props.onClose(cmdGroup); - }); - } - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - this.setState({ - invalidText: `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`, - }); - } + }); + + const name = names.join(" "); + if (name === commandGroup.names.join(" ")) { + const cmdGroup = DecodeResponseCommandGroup(res); this.setState({ updating: false, }); + this.props.onClose(cmdGroup); + } else { + // Rename command Group + const renameRes = await CommandApiService.renameCommandGroup(nodeUrl, name); + const cmdGroup = DecodeResponseCommandGroup(renameRes); + this.setState({ + updating: false, + }); + this.props.onClose(cmdGroup); + } + } catch (err: any) { + console.error(err); + this.setState({ + updating: false, + invalidText: ApiErrorHandler.getErrorMessage(err), }); + } }; handleClose = () => { diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index 07f70ee1..b3c4257f 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -342,12 +342,9 @@ class WSEditorSwaggerPicker extends React.Component Date: Mon, 29 Sep 2025 14:04:35 +1000 Subject: [PATCH 05/18] refactor: update CLIModuleGenerator to use new API pattern --- src/web/src/services/cliApi.ts | 63 ++++++++++++++++ src/web/src/services/index.ts | 1 + src/web/src/views/cli/CLIModuleGenerator.tsx | 79 +++++++------------- 3 files changed, 92 insertions(+), 51 deletions(-) create mode 100644 src/web/src/services/cliApi.ts diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts new file mode 100644 index 00000000..7b4d8fdf --- /dev/null +++ b/src/web/src/services/cliApi.ts @@ -0,0 +1,63 @@ +import axios from "axios"; + +/** + * Service for CLI-related API operations + */ +export class CliApiService { + /** + * Get CLI profiles + */ + static async getCliProfiles(): Promise { + const res = await axios.get(`/CLI/Az/Profiles`); + return res.data; + } + + /** + * Get CLI module view + */ + static async getCliModule(repoName: string, moduleName: string): Promise { + const res = await axios.get(`/CLI/Az/${repoName}/Modules/${moduleName}`); + return res.data; + } + + /** + * Get specs command by path + */ + static async getSpecsCommand(names: string[]): Promise { + const res = await axios.get( + `/AAZ/Specs/CommandTree/Nodes/aaz/${names.slice(0, -1).join("/")}/Leaves/${names[names.length - 1]}`, + ); + return res.data; + } + + /** + * Retrieve multiple commands by names list + */ + static async retrieveCommands(namesList: string[][]): Promise { + const namesListData = namesList.map((names) => ["aaz", ...names]); + const res = await axios.post(`/AAZ/Specs/CommandTree/Nodes/Leaves`, namesListData); + return res.data; + } + + /** + * Get simple command tree + */ + static async getSimpleCommandTree(): Promise { + const res = await axios.get(`/AAZ/Specs/CommandTree/Simple`); + return res.data; + } + + /** + * Update CLI module (generate all) + */ + static async updateCliModule(repoName: string, moduleName: string, data: any): Promise { + await axios.put(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); + } + + /** + * Patch CLI module (generate modified only) + */ + static async patchCliModule(repoName: string, moduleName: string, data: any): Promise { + await axios.patch(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); + } +} diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 463f4a35..5de730c5 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -2,3 +2,4 @@ export { WorkspaceApiService, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { SpecsApiService, SpecsHelper, type Plane, type Resource } from "./specsApi"; export { CommandApiService, ApiErrorHandler } from "./commandApi"; +export { CliApiService } from "./cliApi"; diff --git a/src/web/src/views/cli/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index ef0e96cb..305b7c82 100644 --- a/src/web/src/views/cli/CLIModuleGenerator.tsx +++ b/src/web/src/views/cli/CLIModuleGenerator.tsx @@ -14,7 +14,7 @@ import { Alert, } from "@mui/material"; import { useParams } from "react-router"; -import axios from "axios"; +import { CliApiService, ApiErrorHandler } from "../../services"; import CLIModGeneratorToolBar from "./CLIModGeneratorToolBar"; import CLIModGeneratorProfileCommandTree, { ExportModViewProfile, @@ -92,14 +92,11 @@ interface CLISpecsCommands { } async function retrieveCommand(names: string[]): Promise { - return axios - .get(`/AAZ/Specs/CommandTree/Nodes/aaz/${names.slice(0, -1).join("/")}/Leaves/${names[names.length - 1]}`) - .then((res) => res.data); + return await CliApiService.getSpecsCommand(names); } async function retrieveCommands(namesList: string[][]): Promise { - const namesListData = namesList.map((names) => ["aaz", ...names]); - return axios.post(`/AAZ/Specs/CommandTree/Nodes/Leaves`, namesListData).then((res) => res.data); + return await CliApiService.retrieveCommands(namesList); } const useSpecsCommandTree: () => (namesList: string[][]) => Promise = () => { @@ -167,15 +164,9 @@ const CLIModuleGenerator: React.FC = ({ params }) => { const loadModule = async () => { try { setLoading(true); - const profiles: string[] = await axios.get(`/CLI/Az/Profiles`).then((res) => res.data); - - const modView: CLIModView = await axios - .get(`/CLI/Az/${params.repoName}/Modules/${params.moduleName}`) - .then((res) => res.data); - - const simpleTree: CLISpecsSimpleCommandTree = await axios - .get(`/AAZ/Specs/CommandTree/Simple`) - .then((res) => res.data); + const profiles = await CliApiService.getCliProfiles(); + const modView: CLIModView = await CliApiService.getCliModule(params.repoName, params.moduleName); + const simpleTree: CLISpecsSimpleCommandTree = await CliApiService.getSimpleCommandTree(); Object.keys(modView!.profiles).forEach((profile) => { const idx = profiles.findIndex((v) => v === profile); @@ -197,12 +188,8 @@ const CLIModuleGenerator: React.FC = ({ params }) => { setLoading(false); } catch (err: any) { console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}`); - } else { - setInvalidText(`Error: ${err}`); - } + setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setLoading(false); } }; @@ -325,7 +312,7 @@ function GenerateDialog(props: { props.onClose(false); }; - const handleGenerateAll = () => { + const handleGenerateAll = async () => { const profiles: CLIModViewProfiles = {}; Object.values(props.profileCommandTrees).forEach((tree) => { profiles[tree.name] = ExportModViewProfile(tree); @@ -336,23 +323,18 @@ function GenerateDialog(props: { }; setUpdating(true); - axios - .put(`/CLI/Az/${props.repoName}/Modules/${props.moduleName}`, data) - .then(() => { - setUpdating(false); - props.onClose(true); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } - setUpdating(false); - }); + try { + await CliApiService.updateCliModule(props.repoName, props.moduleName, data); + setUpdating(false); + props.onClose(true); + } catch (err: any) { + console.error(err); + setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setUpdating(false); + } }; - const handleGenerateModified = () => { + const handleGenerateModified = async () => { const profiles: CLIModViewProfiles = {}; Object.values(props.profileCommandTrees).forEach((tree) => { profiles[tree.name] = ExportModViewProfile(tree); @@ -363,20 +345,15 @@ function GenerateDialog(props: { }; setUpdating(true); - axios - .patch(`/CLI/Az/${props.repoName}/Modules/${props.moduleName}`, data) - .then(() => { - setUpdating(false); - props.onClose(true); - }) - .catch((err) => { - console.error(err); - if (err.response?.data?.message) { - const data = err.response!.data!; - setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`); - } - setUpdating(false); - }); + try { + await CliApiService.patchCliModule(props.repoName, props.moduleName, data); + setUpdating(false); + props.onClose(true); + } catch (err: any) { + console.error(err); + setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setUpdating(false); + } }; return ( From 68ac2eb808cb61d447513e1884beef8e9a2e62c3 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 14:14:56 +1000 Subject: [PATCH 06/18] refactor: update CLIModuleSelector to use new API pattern --- src/web/src/services/cliApi.ts | 16 +++++ src/web/src/views/cli/CLIModuleSelector.tsx | 67 ++++++++++----------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts index 7b4d8fdf..1fa9cc64 100644 --- a/src/web/src/services/cliApi.ts +++ b/src/web/src/services/cliApi.ts @@ -12,6 +12,22 @@ export class CliApiService { return res.data; } + /** + * Get CLI modules for a repository + */ + static async getCliModules(repoName: string): Promise { + const res = await axios.get(`/CLI/Az/${repoName}/Modules`); + return res.data; + } + + /** + * Create a new CLI module + */ + static async createCliModule(repoName: string, moduleName: string): Promise { + const res = await axios.post(`/CLI/Az/${repoName}/Modules`, { name: moduleName }); + return res.data; + } + /** * Get CLI module view */ diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index 078336c9..ba5574ea 100644 --- a/src/web/src/views/cli/CLIModuleSelector.tsx +++ b/src/web/src/views/cli/CLIModuleSelector.tsx @@ -9,7 +9,7 @@ import { TextField, Button, } from "@mui/material"; -import axios from "axios"; +import { CliApiService, ApiErrorHandler } from "../../services"; import * as React from "react"; interface CLIModule { @@ -56,48 +56,43 @@ class CLIModuleSelector extends React.Component { - axios - .get("/CLI/Az/" + this.props.repo + "/Modules") - .then((res) => { - const options = res.data.map((option: any) => { - return { - name: option.name, - folder: option.folder, - url: option.url, - }; - }); - this.setState({ - options: options, - }); - }) - .catch((err) => console.error(err)); + loadModules = async () => { + try { + const data = await CliApiService.getCliModules(this.props.repo); + const options = data.map((option: any) => { + return { + name: option.name, + folder: option.folder, + url: option.url, + }; + }); + this.setState({ + options: options, + }); + } catch (err: any) { + console.error(ApiErrorHandler.getErrorMessage(err)); + } }; - handleDialogSubmit = (event: any) => { + handleDialogSubmit = async (event: any) => { const form = event.currentTarget; if (form.checkValidity() === true) { const moduleName = this.state.createDialogValue.name; - axios - .post("/CLI/Az/" + this.props.repo + "/Modules", { - name: moduleName, - }) - .then((res) => { - const module = res.data; - const value = { - name: module.name, - folder: module.folder, - url: module.url, - }; - setTimeout(() => { - this.onValueUpdated(value); - }); - this.handleDialogClose(); - }) - .catch((error) => { - console.error(error.response); + try { + const module = await CliApiService.createCliModule(this.props.repo, moduleName); + const value = { + name: module.name, + folder: module.folder, + url: module.url, + }; + setTimeout(() => { + this.onValueUpdated(value); }); + this.handleDialogClose(); + } catch (err: any) { + console.error(ApiErrorHandler.getErrorMessage(err)); + } } }; From 4fd042aa0af0d11b69dec9f1a8dbcc94260ded85 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 14:39:07 +1000 Subject: [PATCH 07/18] refactor: remove redundant comments from services/* --- src/web/src/services/cliApi.ts | 30 ------------ src/web/src/services/commandApi.ts | 69 ---------------------------- src/web/src/services/index.ts | 1 - src/web/src/services/specsApi.ts | 43 ----------------- src/web/src/services/workspaceApi.ts | 62 ------------------------- 5 files changed, 205 deletions(-) diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts index 1fa9cc64..a5b68e25 100644 --- a/src/web/src/services/cliApi.ts +++ b/src/web/src/services/cliApi.ts @@ -1,44 +1,26 @@ import axios from "axios"; -/** - * Service for CLI-related API operations - */ export class CliApiService { - /** - * Get CLI profiles - */ static async getCliProfiles(): Promise { const res = await axios.get(`/CLI/Az/Profiles`); return res.data; } - /** - * Get CLI modules for a repository - */ static async getCliModules(repoName: string): Promise { const res = await axios.get(`/CLI/Az/${repoName}/Modules`); return res.data; } - /** - * Create a new CLI module - */ static async createCliModule(repoName: string, moduleName: string): Promise { const res = await axios.post(`/CLI/Az/${repoName}/Modules`, { name: moduleName }); return res.data; } - /** - * Get CLI module view - */ static async getCliModule(repoName: string, moduleName: string): Promise { const res = await axios.get(`/CLI/Az/${repoName}/Modules/${moduleName}`); return res.data; } - /** - * Get specs command by path - */ static async getSpecsCommand(names: string[]): Promise { const res = await axios.get( `/AAZ/Specs/CommandTree/Nodes/aaz/${names.slice(0, -1).join("/")}/Leaves/${names[names.length - 1]}`, @@ -46,33 +28,21 @@ export class CliApiService { return res.data; } - /** - * Retrieve multiple commands by names list - */ static async retrieveCommands(namesList: string[][]): Promise { const namesListData = namesList.map((names) => ["aaz", ...names]); const res = await axios.post(`/AAZ/Specs/CommandTree/Nodes/Leaves`, namesListData); return res.data; } - /** - * Get simple command tree - */ static async getSimpleCommandTree(): Promise { const res = await axios.get(`/AAZ/Specs/CommandTree/Simple`); return res.data; } - /** - * Update CLI module (generate all) - */ static async updateCliModule(repoName: string, moduleName: string, data: any): Promise { await axios.put(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); } - /** - * Patch CLI module (generate modified only) - */ static async patchCliModule(repoName: string, moduleName: string, data: any): Promise { await axios.patch(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); } diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index a7ceb894..3f579500 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -1,59 +1,35 @@ import axios from "axios"; -/** - * Service for command-related API operations - */ export class CommandApiService { - /** - * Get command details - */ static async getCommand(leafUrl: string): Promise { const res = await axios.get(leafUrl); return res.data; } - /** - * Get commands for a resource URL - */ static async getCommandsForResource(resourceUrl: string): Promise { const res = await axios.get(`${resourceUrl}/Commands`); return res.data; } - /** - * Delete resource - */ static async deleteResource(resourceUrl: string): Promise { await axios.delete(resourceUrl); } - /** - * Update command properties - */ static async updateCommand(leafUrl: string, data: any): Promise { const res = await axios.patch(leafUrl, data); return res.data; } - /** - * Rename command - */ static async renameCommand(leafUrl: string, newName: string): Promise { const res = await axios.post(`${leafUrl}/Rename`, { name: newName }); return res.data; } - /** - * Update command examples - */ static async updateCommandExamples(leafUrl: string, examples: any[]): Promise { const res = await axios.patch(leafUrl, { examples }); return res.data; } - /** - * Generate examples from swagger - */ static async generateSwaggerExamples(leafUrl: string): Promise { const res = await axios.post(`${leafUrl}/GenerateExamples`, { source: "swagger" }); return res.data.map((v: any) => ({ @@ -62,38 +38,23 @@ export class CommandApiService { })); } - /** - * Add subcommands - */ static async addSubcommands(resourceUrl: string, data: any): Promise { await axios.post(resourceUrl, data); } - /** - * Update command outputs - */ static async updateCommandOutputs(leafUrl: string, outputs: any[]): Promise { const res = await axios.patch(leafUrl, { outputs }); return res.data; } - /** - * Update command argument - */ static async updateCommandArgument(argumentUrl: string, data: any): Promise { await axios.patch(argumentUrl, data); } - /** - * Update argument by ID - */ static async updateArgumentById(argId: string, data: any): Promise { await axios.patch(argId, data); } - /** - * Flatten argument structure - */ static async flattenArgument(flattenUrl: string, data?: any): Promise { if (data) { await axios.post(flattenUrl, data); @@ -102,23 +63,14 @@ export class CommandApiService { } } - /** - * Unwrap class argument - */ static async unwrapClassArgument(flattenUrl: string): Promise { await axios.post(flattenUrl); } - /** - * Delete command group/resource node - */ static async deleteCommandGroup(nodeUrl: string): Promise { await axios.delete(nodeUrl); } - /** - * Update command group properties - */ static async updateCommandGroup( nodeUrl: string, data: { help: { short: string; lines: string[] }; stage: string }, @@ -127,26 +79,17 @@ export class CommandApiService { return res.data; } - /** - * Rename command group - */ static async renameCommandGroup(nodeUrl: string, name: string): Promise { const res = await axios.post(`${nodeUrl}/Rename`, { name }); return res.data; } - /** - * Find similar arguments for a command argument - */ static async findSimilarArguments(commandUrl: string, argVar: string): Promise { const similarUrl = `${commandUrl}/Arguments/${argVar}/FindSimilar`; const res = await axios.post(similarUrl); return res.data; } - /** - * Create a subresource for a command resource - */ static async createSubresource( subresourceUrl: string, data: { @@ -164,13 +107,7 @@ export class CommandApiService { } } -/** - * Error handling utilities for API operations - */ export class ApiErrorHandler { - /** - * Extract error message from axios error response - */ static getErrorMessage(err: any): string { if (err.response?.data?.message) { const data = err.response.data; @@ -180,16 +117,10 @@ export class ApiErrorHandler { return "An unexpected error occurred"; } - /** - * Check if error is a specific HTTP status code - */ static isHttpError(err: any, statusCode: number): boolean { return err.response?.status === statusCode; } - /** - * Handle common API error scenarios - */ static handleApiError(err: any, context: string = ""): never { console.error(context, err); const message = this.getErrorMessage(err); diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 5de730c5..834701e4 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,4 +1,3 @@ -// Barrel export for all API services export { WorkspaceApiService, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { SpecsApiService, SpecsHelper, type Plane, type Resource } from "./specsApi"; export { CommandApiService, ApiErrorHandler } from "./commandApi"; diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts index d490e878..c3d70996 100644 --- a/src/web/src/services/specsApi.ts +++ b/src/web/src/services/specsApi.ts @@ -1,6 +1,5 @@ import axios from "axios"; -// Types for specs/planes/modules operations export interface Plane { name: string; displayName: string; @@ -12,13 +11,7 @@ export interface Resource { version: string; } -/** - * Service for API specifications, planes, modules, and resource providers - */ export class SpecsApiService { - /** - * Get all available planes - */ static async getPlanes(): Promise { const res = await axios.get(`/AAZ/Specs/Planes`); return res.data.map((v: any) => ({ @@ -28,49 +21,31 @@ export class SpecsApiService { })); } - /** - * Get plane names only (used for client configurable plane detection) - */ static async getPlaneNames(): Promise { const res = await axios.get(`/AAZ/Specs/Planes`); return res.data.map((v: any) => v.name); } - /** - * Get modules for a specific plane - */ static async getModulesForPlane(planeName: string): Promise { const res = await axios.get(`/Swagger/Specs/${planeName}`); return res.data.map((v: any) => v.url); } - /** - * Get resource providers for a specific module - */ static async getResourceProviders(moduleUrl: string): Promise { const res = await axios.get(`${moduleUrl}/ResourceProviders`); return res.data.map((v: any) => v.url); } - /** - * Get resources for a specific resource provider - */ static async getResources(resourceProviderUrl: string): Promise { const res = await axios.get(`${resourceProviderUrl}/Resources`); return res.data; } - /** - * Get swagger modules for a plane - */ static async getSwaggerModules(plane: string): Promise { const res = await axios.get(`/Swagger/Specs/${plane}`); return res.data.map((v: any) => v.url); } - /** - * Get resource providers for a module (with optional type filter) - */ static async getResourceProvidersWithType(moduleUrl: string, type?: string): Promise { let url = `${moduleUrl}/ResourceProviders`; if (type) { @@ -80,17 +55,11 @@ export class SpecsApiService { return res.data.map((v: any) => v.url); } - /** - * Get resources for a resource provider (returns raw data) - */ static async getProviderResources(resourceProviderUrl: string): Promise { const res = await axios.get(`${resourceProviderUrl}/Resources`); return res.data; } - /** - * Filter resources by plane - */ static async filterResourcesByPlane(plane: string, resourceIds: string[]): Promise { const filterBody = { resources: resourceIds }; const res = await axios.post(`/AAZ/Specs/Resources/${plane}/Filter`, filterBody); @@ -98,28 +67,16 @@ export class SpecsApiService { } } -/** - * Helper functions for working with specs - */ export class SpecsHelper { - /** - * Remove common prefix from option strings - */ static removeCommonPrefix(options: string[], prefix: string): string[] { return options.map((option) => option.replace(prefix, "")); } - /** - * Check if a plane is client configurable (not in built-in planes) - */ static async isClientConfigurablePlane(planeName: string): Promise { const planeNames = await SpecsApiService.getPlaneNames(); return !planeNames.includes(planeName); } - /** - * Build full resource provider URL - */ static buildResourceProviderUrl(plane: string, modNames: string[], rpName: string, source: string): string { const basePath = `/Swagger/Specs/${plane}/${modNames.join("/")}`; const resourceProviderPath = `/ResourceProviders/${rpName}`; diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts index 087b5310..6e68eb45 100644 --- a/src/web/src/services/workspaceApi.ts +++ b/src/web/src/services/workspaceApi.ts @@ -1,6 +1,5 @@ import axios from "axios"; -// Types for workspace operations export interface Workspace { name: string; plane: string | null; @@ -26,13 +25,7 @@ export interface ClientConfig { auth: any; } -/** - * Service for workspace-related API operations - */ export class WorkspaceApiService { - /** - * Get all workspaces - */ static async getWorkspaces(): Promise { const res = await axios.get("/AAZ/Editor/Workspaces"); return res.data.map((option: any) => ({ @@ -44,9 +37,6 @@ export class WorkspaceApiService { })); } - /** - * Create a new workspace - */ static async createWorkspace(data: CreateWorkspaceData): Promise { const res = await axios.post("/AAZ/Editor/Workspaces", data); const workspace = res.data; @@ -61,33 +51,21 @@ export class WorkspaceApiService { }; } - /** - * Get workspace details - */ static async getWorkspace(workspaceUrl: string): Promise { const res = await axios.get(workspaceUrl); return res.data; } - /** - * Delete a workspace - */ static async deleteWorkspace(workspaceName: string): Promise { const nodeUrl = `/AAZ/Editor/Workspaces/${workspaceName}`; await axios.delete(nodeUrl); } - /** - * Rename a workspace - */ static async renameWorkspace(workspaceUrl: string, newName: string): Promise<{ name: string }> { const res = await axios.post(`${workspaceUrl}/Rename`, { name: newName }); return res.data; } - /** - * Get workspace client config - */ static async getWorkspaceClientConfig(workspaceUrl: string): Promise { try { const res = await axios.get(`${workspaceUrl}/ClientConfig`); @@ -109,7 +87,6 @@ export class WorkspaceApiService { return clientConfig; } catch (err: any) { - // catch 404 error if (err.response?.status === 404) { return null; } @@ -117,102 +94,63 @@ export class WorkspaceApiService { } } - /** - * Update workspace client config - */ static async updateClientConfig(workspaceUrl: string, config: any): Promise { await axios.post(`${workspaceUrl}/ClientConfig`, config); } - /** - * Verify client config compatibility - */ static async verifyClientConfig(workspaceUrl: string): Promise { const url = `${workspaceUrl}/ClientConfig/AAZ/Compare`; await axios.post(url); } - /** - * Inherit client config from AAZ - */ static async inheritClientConfig(workspaceUrl: string): Promise { const url = `${workspaceUrl}/ClientConfig/AAZ/Inherit`; await axios.post(url); } - /** - * Generate/export workspace - */ static async generateWorkspace(workspaceUrl: string): Promise { const url = `${workspaceUrl}/Generate`; await axios.post(url); } - /** - * Get workspace resources for reloading - */ static async getWorkspaceResources(workspaceUrl: string): Promise { const res = await axios.get(`${workspaceUrl}/CommandTree/Nodes/aaz/Resources`); return res.data; } - /** - * Get workspace swagger default settings - */ static async getWorkspaceSwaggerDefault(workspaceName: string): Promise { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); return res.data; } - /** - * Reload swagger resources - */ static async reloadSwaggerResources(workspaceUrl: string, data: any): Promise { const reloadUrl = `${workspaceUrl}/Resources/ReloadSwagger`; await axios.post(reloadUrl, data); } - /** - * Reload TypeSpec resources - */ static async reloadTypespecResources(workspaceUrl: string, data: any): Promise { const reloadUrl = `${workspaceUrl}/Resources/ReloadTypespec`; await axios.post(reloadUrl, data); } - /** - * Get workspace swagger default settings - */ static async getSwaggerDefault(workspaceName: string): Promise { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); return res.data; } - /** - * Get workspace resources by workspace name (for swagger picker) - */ static async getWorkspaceResourcesByName(workspaceName: string): Promise { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/Resources`); return res.data; } - /** - * Add swagger resources to workspace - */ static async addSwaggerResources(workspaceName: string, requestBody: any): Promise { await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddSwagger`, requestBody); } - /** - * Add TypeSpec resources to workspace - */ static async addTypespecResources(workspaceName: string, requestBody: any): Promise { await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, requestBody); } - /** - * Get workspace client configuration - */ static async getClientConfig(workspaceUrl: string): Promise { const res = await axios.get(`${workspaceUrl}/ClientConfig`); return res.data; From f3b2d0593b4c76f8a001d528ef59ff4f20e197bd Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 14:44:42 +1000 Subject: [PATCH 08/18] refactor: remove comments --- src/web/src/views/workspace/WSEditorCommandContent.tsx | 1 - src/web/src/views/workspace/WSEditorCommandGroupContent.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 5042ee52..f3c3ec44 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -954,7 +954,6 @@ class CommandDialog extends React.Component Date: Mon, 29 Sep 2025 15:12:41 +1000 Subject: [PATCH 09/18] refactor: change service layer to object-based from class-based --- src/web/src/services/cliApi.ts | 40 +++---- src/web/src/services/commandApi.ts | 100 +++++++++--------- src/web/src/services/index.ts | 8 +- src/web/src/services/specsApi.ts | 58 +++++----- src/web/src/services/workspaceApi.ts | 80 +++++++------- src/web/src/views/cli/CLIInstruction.tsx | 2 + .../cli/CLIModGeneratorProfileCommandTree.tsx | 2 + .../views/cli/CLIModGeneratorProfileTabs.tsx | 2 + .../src/views/cli/CLIModGeneratorToolBar.tsx | 2 + src/web/src/views/cli/CLIModuleCommon.tsx | 2 + src/web/src/views/cli/CLIModuleGenerator.tsx | 24 +++-- src/web/src/views/cli/CLIModuleSelector.tsx | 12 ++- src/web/src/views/cli/CLIPage.tsx | 2 + src/web/src/views/commands/CommandsPage.tsx | 2 + src/web/src/views/home/HomePage.tsx | 2 + src/web/src/views/workspace/WSEditor.tsx | 44 ++++---- .../views/workspace/WSEditorClientConfig.tsx | 28 ++--- .../WSEditorCommandArgumentsContent.tsx | 32 +++--- .../workspace/WSEditorCommandContent.tsx | 32 +++--- .../workspace/WSEditorCommandGroupContent.tsx | 12 ++- .../views/workspace/WSEditorCommandTree.tsx | 2 + .../views/workspace/WSEditorExamplePicker.tsx | 2 + .../views/workspace/WSEditorSwaggerPicker.tsx | 38 +++---- src/web/src/views/workspace/WSEditorTheme.tsx | 2 + .../src/views/workspace/WSEditorToolBar.tsx | 2 + .../views/workspace/WorkspaceInstruction.tsx | 2 + src/web/src/views/workspace/WorkspacePage.tsx | 2 + .../src/views/workspace/WorkspaceSelector.tsx | 22 ++-- .../argument/WSECArgumentSimilarPicker.tsx | 2 + 29 files changed, 303 insertions(+), 257 deletions(-) diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts index a5b68e25..18cfd6cb 100644 --- a/src/web/src/services/cliApi.ts +++ b/src/web/src/services/cliApi.ts @@ -1,49 +1,49 @@ import axios from "axios"; -export class CliApiService { - static async getCliProfiles(): Promise { +export const cliApi = { + getCliProfiles: async (): Promise => { const res = await axios.get(`/CLI/Az/Profiles`); return res.data; - } + }, - static async getCliModules(repoName: string): Promise { + getCliModules: async (repoName: string): Promise => { const res = await axios.get(`/CLI/Az/${repoName}/Modules`); return res.data; - } + }, - static async createCliModule(repoName: string, moduleName: string): Promise { + createCliModule: async (repoName: string, moduleName: string): Promise => { const res = await axios.post(`/CLI/Az/${repoName}/Modules`, { name: moduleName }); return res.data; - } + }, - static async getCliModule(repoName: string, moduleName: string): Promise { + getCliModule: async (repoName: string, moduleName: string): Promise => { const res = await axios.get(`/CLI/Az/${repoName}/Modules/${moduleName}`); return res.data; - } + }, - static async getSpecsCommand(names: string[]): Promise { + getSpecsCommand: async (names: string[]): Promise => { const res = await axios.get( `/AAZ/Specs/CommandTree/Nodes/aaz/${names.slice(0, -1).join("/")}/Leaves/${names[names.length - 1]}`, ); return res.data; - } + }, - static async retrieveCommands(namesList: string[][]): Promise { + retrieveCommands: async (namesList: string[][]): Promise => { const namesListData = namesList.map((names) => ["aaz", ...names]); const res = await axios.post(`/AAZ/Specs/CommandTree/Nodes/Leaves`, namesListData); return res.data; - } + }, - static async getSimpleCommandTree(): Promise { + getSimpleCommandTree: async (): Promise => { const res = await axios.get(`/AAZ/Specs/CommandTree/Simple`); return res.data; - } + }, - static async updateCliModule(repoName: string, moduleName: string, data: any): Promise { + updateCliModule: async (repoName: string, moduleName: string, data: any): Promise => { await axios.put(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); - } + }, - static async patchCliModule(repoName: string, moduleName: string, data: any): Promise { + patchCliModule: async (repoName: string, moduleName: string, data: any): Promise => { await axios.patch(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); - } -} + }, +} as const; diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index 3f579500..b5fdef48 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -1,129 +1,129 @@ import axios from "axios"; -export class CommandApiService { - static async getCommand(leafUrl: string): Promise { +export const commandApi = { + getCommand: async (leafUrl: string): Promise => { const res = await axios.get(leafUrl); return res.data; - } + }, - static async getCommandsForResource(resourceUrl: string): Promise { + getCommandsForResource: async (resourceUrl: string): Promise => { const res = await axios.get(`${resourceUrl}/Commands`); return res.data; - } + }, - static async deleteResource(resourceUrl: string): Promise { + deleteResource: async (resourceUrl: string): Promise => { await axios.delete(resourceUrl); - } + }, - static async updateCommand(leafUrl: string, data: any): Promise { + updateCommand: async (leafUrl: string, data: any): Promise => { const res = await axios.patch(leafUrl, data); return res.data; - } + }, - static async renameCommand(leafUrl: string, newName: string): Promise { + renameCommand: async (leafUrl: string, newName: string): Promise => { const res = await axios.post(`${leafUrl}/Rename`, { name: newName }); return res.data; - } + }, - static async updateCommandExamples(leafUrl: string, examples: any[]): Promise { + updateCommandExamples: async (leafUrl: string, examples: any[]): Promise => { const res = await axios.patch(leafUrl, { examples }); return res.data; - } + }, - static async generateSwaggerExamples(leafUrl: string): Promise { + generateSwaggerExamples: async (leafUrl: string): Promise => { const res = await axios.post(`${leafUrl}/GenerateExamples`, { source: "swagger" }); return res.data.map((v: any) => ({ name: v.name, commands: v.commands, })); - } + }, - static async addSubcommands(resourceUrl: string, data: any): Promise { + addSubcommands: async (resourceUrl: string, data: any): Promise => { await axios.post(resourceUrl, data); - } + }, - static async updateCommandOutputs(leafUrl: string, outputs: any[]): Promise { + updateCommandOutputs: async (leafUrl: string, outputs: any[]): Promise => { const res = await axios.patch(leafUrl, { outputs }); return res.data; - } + }, - static async updateCommandArgument(argumentUrl: string, data: any): Promise { + updateCommandArgument: async (argumentUrl: string, data: any): Promise => { await axios.patch(argumentUrl, data); - } + }, - static async updateArgumentById(argId: string, data: any): Promise { + updateArgumentById: async (argId: string, data: any): Promise => { await axios.patch(argId, data); - } + }, - static async flattenArgument(flattenUrl: string, data?: any): Promise { + flattenArgument: async (flattenUrl: string, data?: any): Promise => { if (data) { await axios.post(flattenUrl, data); } else { await axios.post(flattenUrl); } - } + }, - static async unwrapClassArgument(flattenUrl: string): Promise { + unwrapClassArgument: async (flattenUrl: string): Promise => { await axios.post(flattenUrl); - } + }, - static async deleteCommandGroup(nodeUrl: string): Promise { + deleteCommandGroup: async (nodeUrl: string): Promise => { await axios.delete(nodeUrl); - } + }, - static async updateCommandGroup( + updateCommandGroup: async ( nodeUrl: string, data: { help: { short: string; lines: string[] }; stage: string }, - ): Promise { + ): Promise => { const res = await axios.patch(nodeUrl, data); return res.data; - } + }, - static async renameCommandGroup(nodeUrl: string, name: string): Promise { + renameCommandGroup: async (nodeUrl: string, name: string): Promise => { const res = await axios.post(`${nodeUrl}/Rename`, { name }); return res.data; - } + }, - static async findSimilarArguments(commandUrl: string, argVar: string): Promise { + findSimilarArguments: async (commandUrl: string, argVar: string): Promise => { const similarUrl = `${commandUrl}/Arguments/${argVar}/FindSimilar`; const res = await axios.post(similarUrl); return res.data; - } + }, - static async createSubresource( + createSubresource: async ( subresourceUrl: string, data: { commandGroupName: string; refArgsOptions: { [argVar: string]: string[] }; arg: string; }, - ): Promise { + ): Promise => { try { const response = await axios.post(subresourceUrl, data); return response.data; } catch (err: any) { - ApiErrorHandler.handleApiError(err, "Failed to create subresource"); + apiErrorHandler.handleApiError(err, "Failed to create subresource"); } - } -} + }, +} as const; -export class ApiErrorHandler { - static getErrorMessage(err: any): string { +export const apiErrorHandler = { + getErrorMessage: (err: any): string => { if (err.response?.data?.message) { const data = err.response.data; const details = data.details ? `: ${JSON.stringify(data.details)}` : ""; return `ResponseError: ${data.message}${details}`; } return "An unexpected error occurred"; - } + }, - static isHttpError(err: any, statusCode: number): boolean { + isHttpError: (err: any, statusCode: number): boolean => { return err.response?.status === statusCode; - } + }, - static handleApiError(err: any, context: string = ""): never { + handleApiError: (err: any, context: string = ""): never => { console.error(context, err); - const message = this.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); throw new Error(message); - } -} + }, +} as const; diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 834701e4..7f353399 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,4 +1,4 @@ -export { WorkspaceApiService, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; -export { SpecsApiService, SpecsHelper, type Plane, type Resource } from "./specsApi"; -export { CommandApiService, ApiErrorHandler } from "./commandApi"; -export { CliApiService } from "./cliApi"; +export { workspaceApi, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; +export { specsApi, specsHelper, type Plane, type Resource } from "./specsApi"; +export { commandApi, apiErrorHandler } from "./commandApi"; +export { cliApi } from "./cliApi"; diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts index c3d70996..9d577552 100644 --- a/src/web/src/services/specsApi.ts +++ b/src/web/src/services/specsApi.ts @@ -11,76 +11,76 @@ export interface Resource { version: string; } -export class SpecsApiService { - static async getPlanes(): Promise { +export const specsApi = { + getPlanes: async (): Promise => { const res = await axios.get(`/AAZ/Specs/Planes`); return res.data.map((v: any) => ({ name: v.name, displayName: v.displayName, moduleOptions: undefined, })); - } + }, - static async getPlaneNames(): Promise { + getPlaneNames: async (): Promise => { const res = await axios.get(`/AAZ/Specs/Planes`); return res.data.map((v: any) => v.name); - } + }, - static async getModulesForPlane(planeName: string): Promise { + getModulesForPlane: async (planeName: string): Promise => { const res = await axios.get(`/Swagger/Specs/${planeName}`); return res.data.map((v: any) => v.url); - } + }, - static async getResourceProviders(moduleUrl: string): Promise { + getResourceProviders: async (moduleUrl: string): Promise => { const res = await axios.get(`${moduleUrl}/ResourceProviders`); return res.data.map((v: any) => v.url); - } + }, - static async getResources(resourceProviderUrl: string): Promise { + getResources: async (resourceProviderUrl: string): Promise => { const res = await axios.get(`${resourceProviderUrl}/Resources`); return res.data; - } + }, - static async getSwaggerModules(plane: string): Promise { + getSwaggerModules: async (plane: string): Promise => { const res = await axios.get(`/Swagger/Specs/${plane}`); return res.data.map((v: any) => v.url); - } + }, - static async getResourceProvidersWithType(moduleUrl: string, type?: string): Promise { + getResourceProvidersWithType: async (moduleUrl: string, type?: string): Promise => { let url = `${moduleUrl}/ResourceProviders`; if (type) { url += `?type=${type}`; } const res = await axios.get(url); return res.data.map((v: any) => v.url); - } + }, - static async getProviderResources(resourceProviderUrl: string): Promise { + getProviderResources: async (resourceProviderUrl: string): Promise => { const res = await axios.get(`${resourceProviderUrl}/Resources`); return res.data; - } + }, - static async filterResourcesByPlane(plane: string, resourceIds: string[]): Promise { + filterResourcesByPlane: async (plane: string, resourceIds: string[]): Promise => { const filterBody = { resources: resourceIds }; const res = await axios.post(`/AAZ/Specs/Resources/${plane}/Filter`, filterBody); return res.data; - } -} + }, +} as const; -export class SpecsHelper { - static removeCommonPrefix(options: string[], prefix: string): string[] { +export const specsHelper = { + removeCommonPrefix: (options: string[], prefix: string): string[] => { return options.map((option) => option.replace(prefix, "")); - } + }, - static async isClientConfigurablePlane(planeName: string): Promise { - const planeNames = await SpecsApiService.getPlaneNames(); + isClientConfigurablePlane: async (planeName: string): Promise => { + const planeNames = await specsApi.getPlaneNames(); return !planeNames.includes(planeName); - } + }, - static buildResourceProviderUrl(plane: string, modNames: string[], rpName: string, source: string): string { + buildResourceProviderUrl: (plane: string, modNames: string[], rpName: string, source: string): string => { const basePath = `/Swagger/Specs/${plane}/${modNames.join("/")}`; const resourceProviderPath = `/ResourceProviders/${rpName}`; const suffix = source.toLowerCase() === "typespec" ? "/TypeSpec" : ""; return `${basePath}${resourceProviderPath}${suffix}`; - } -} + }, +} as const; diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts index 6e68eb45..cf5eff56 100644 --- a/src/web/src/services/workspaceApi.ts +++ b/src/web/src/services/workspaceApi.ts @@ -25,8 +25,8 @@ export interface ClientConfig { auth: any; } -export class WorkspaceApiService { - static async getWorkspaces(): Promise { +export const workspaceApi = { + getWorkspaces: async (): Promise => { const res = await axios.get("/AAZ/Editor/Workspaces"); return res.data.map((option: any) => ({ name: option.name, @@ -35,9 +35,9 @@ export class WorkspaceApiService { plane: option.plane, folder: option.folder, })); - } + }, - static async createWorkspace(data: CreateWorkspaceData): Promise { + createWorkspace: async (data: CreateWorkspaceData): Promise => { const res = await axios.post("/AAZ/Editor/Workspaces", data); const workspace = res.data; return { @@ -49,24 +49,24 @@ export class WorkspaceApiService { url: workspace.url, folder: workspace.folder, }; - } + }, - static async getWorkspace(workspaceUrl: string): Promise { + getWorkspace: async (workspaceUrl: string): Promise => { const res = await axios.get(workspaceUrl); return res.data; - } + }, - static async deleteWorkspace(workspaceName: string): Promise { + deleteWorkspace: async (workspaceName: string): Promise => { const nodeUrl = `/AAZ/Editor/Workspaces/${workspaceName}`; await axios.delete(nodeUrl); - } + }, - static async renameWorkspace(workspaceUrl: string, newName: string): Promise<{ name: string }> { + renameWorkspace: async (workspaceUrl: string, newName: string): Promise<{ name: string }> => { const res = await axios.post(`${workspaceUrl}/Rename`, { name: newName }); return res.data; - } + }, - static async getWorkspaceClientConfig(workspaceUrl: string): Promise { + getWorkspaceClientConfig: async (workspaceUrl: string): Promise => { try { const res = await axios.get(`${workspaceUrl}/ClientConfig`); const clientConfig: ClientConfig = { @@ -92,67 +92,67 @@ export class WorkspaceApiService { } throw err; } - } + }, - static async updateClientConfig(workspaceUrl: string, config: any): Promise { + updateClientConfig: async (workspaceUrl: string, config: any): Promise => { await axios.post(`${workspaceUrl}/ClientConfig`, config); - } + }, - static async verifyClientConfig(workspaceUrl: string): Promise { + verifyClientConfig: async (workspaceUrl: string): Promise => { const url = `${workspaceUrl}/ClientConfig/AAZ/Compare`; await axios.post(url); - } + }, - static async inheritClientConfig(workspaceUrl: string): Promise { + inheritClientConfig: async (workspaceUrl: string): Promise => { const url = `${workspaceUrl}/ClientConfig/AAZ/Inherit`; await axios.post(url); - } + }, - static async generateWorkspace(workspaceUrl: string): Promise { + generateWorkspace: async (workspaceUrl: string): Promise => { const url = `${workspaceUrl}/Generate`; await axios.post(url); - } + }, - static async getWorkspaceResources(workspaceUrl: string): Promise { + getWorkspaceResources: async (workspaceUrl: string): Promise => { const res = await axios.get(`${workspaceUrl}/CommandTree/Nodes/aaz/Resources`); return res.data; - } + }, - static async getWorkspaceSwaggerDefault(workspaceName: string): Promise { + getWorkspaceSwaggerDefault: async (workspaceName: string): Promise => { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); return res.data; - } + }, - static async reloadSwaggerResources(workspaceUrl: string, data: any): Promise { + reloadSwaggerResources: async (workspaceUrl: string, data: any): Promise => { const reloadUrl = `${workspaceUrl}/Resources/ReloadSwagger`; await axios.post(reloadUrl, data); - } + }, - static async reloadTypespecResources(workspaceUrl: string, data: any): Promise { + reloadTypespecResources: async (workspaceUrl: string, data: any): Promise => { const reloadUrl = `${workspaceUrl}/Resources/ReloadTypespec`; await axios.post(reloadUrl, data); - } + }, - static async getSwaggerDefault(workspaceName: string): Promise { + getSwaggerDefault: async (workspaceName: string): Promise => { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); return res.data; - } + }, - static async getWorkspaceResourcesByName(workspaceName: string): Promise { + getWorkspaceResourcesByName: async (workspaceName: string): Promise => { const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/Resources`); return res.data; - } + }, - static async addSwaggerResources(workspaceName: string, requestBody: any): Promise { + addSwaggerResources: async (workspaceName: string, requestBody: any): Promise => { await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddSwagger`, requestBody); - } + }, - static async addTypespecResources(workspaceName: string, requestBody: any): Promise { + addTypespecResources: async (workspaceName: string, requestBody: any): Promise => { await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, requestBody); - } + }, - static async getClientConfig(workspaceUrl: string): Promise { + getClientConfig: async (workspaceUrl: string): Promise => { const res = await axios.get(`${workspaceUrl}/ClientConfig`); return res.data; - } -} + }, +} as const; diff --git a/src/web/src/views/cli/CLIInstruction.tsx b/src/web/src/views/cli/CLIInstruction.tsx index 37954e73..0e7b850c 100644 --- a/src/web/src/views/cli/CLIInstruction.tsx +++ b/src/web/src/views/cli/CLIInstruction.tsx @@ -67,3 +67,5 @@ class CLIInstruction extends React.Component { } export default CLIInstruction; + + diff --git a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx index 39f2f1b0..f2252087 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx @@ -973,3 +973,5 @@ export default CLIModGeneratorProfileCommandTree; export type { ProfileCommandTree }; export { InitializeCommandTreeByModView, BuildProfileCommandTree, ExportModViewProfile }; + + diff --git a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx index a1cd079a..dae6a06d 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx @@ -39,3 +39,5 @@ class CLIModGeneratorProfileTabs extends React.Component { - return await CliApiService.getSpecsCommand(names); + return await cliApi.getSpecsCommand(names); } async function retrieveCommands(namesList: string[][]): Promise { - return await CliApiService.retrieveCommands(namesList); + return await cliApi.retrieveCommands(namesList); } const useSpecsCommandTree: () => (namesList: string[][]) => Promise = () => { @@ -164,9 +164,9 @@ const CLIModuleGenerator: React.FC = ({ params }) => { const loadModule = async () => { try { setLoading(true); - const profiles = await CliApiService.getCliProfiles(); - const modView: CLIModView = await CliApiService.getCliModule(params.repoName, params.moduleName); - const simpleTree: CLISpecsSimpleCommandTree = await CliApiService.getSimpleCommandTree(); + const profiles = await cliApi.getCliProfiles(); + const modView: CLIModView = await cliApi.getCliModule(params.repoName, params.moduleName); + const simpleTree: CLISpecsSimpleCommandTree = await cliApi.getSimpleCommandTree(); Object.keys(modView!.profiles).forEach((profile) => { const idx = profiles.findIndex((v) => v === profile); @@ -188,7 +188,7 @@ const CLIModuleGenerator: React.FC = ({ params }) => { setLoading(false); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setLoading(false); } }; @@ -324,12 +324,12 @@ function GenerateDialog(props: { setUpdating(true); try { - await CliApiService.updateCliModule(props.repoName, props.moduleName, data); + await cliApi.updateCliModule(props.repoName, props.moduleName, data); setUpdating(false); props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -346,12 +346,12 @@ function GenerateDialog(props: { setUpdating(true); try { - await CliApiService.patchCliModule(props.repoName, props.moduleName, data); + await cliApi.patchCliModule(props.repoName, props.moduleName, data); setUpdating(false); props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -398,3 +398,5 @@ export type { CLISpecsSimpleCommand, }; export { CLIModuleGeneratorWrapper as CLIModuleGenerator }; + + diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index ba5574ea..4daa5132 100644 --- a/src/web/src/views/cli/CLIModuleSelector.tsx +++ b/src/web/src/views/cli/CLIModuleSelector.tsx @@ -9,7 +9,7 @@ import { TextField, Button, } from "@mui/material"; -import { CliApiService, ApiErrorHandler } from "../../services"; +import { cliApi, apiErrorHandler } from "../../services"; import * as React from "react"; interface CLIModule { @@ -58,7 +58,7 @@ class CLIModuleSelector extends React.Component { try { - const data = await CliApiService.getCliModules(this.props.repo); + const data = await cliApi.getCliModules(this.props.repo); const options = data.map((option: any) => { return { name: option.name, @@ -70,7 +70,7 @@ class CLIModuleSelector extends React.Component { } try { - const planeNames = await SpecsApiService.getPlaneNames(); - const workspaceData = await WorkspaceApiService.getWorkspace(workspaceUrl); + const planeNames = await specsApi.getPlaneNames(); + const workspaceData = await workspaceApi.getWorkspace(workspaceUrl); const reloadTimestamp = Date.now(); const commandMap: CommandMap = {}; const commandGroupMap: CommandGroupMap = {}; @@ -294,7 +294,7 @@ class WSEditor extends React.Component { }; getWorkspaceClientConfig = async (workspaceUrl: string) => { - return await WorkspaceApiService.getWorkspaceClientConfig(workspaceUrl); + return await workspaceApi.getWorkspaceClientConfig(workspaceUrl); }; showClientConfigDialog = () => { @@ -599,11 +599,11 @@ class WSEditorExportDialog extends React.Component { this.setState({ updating: true }); try { - await WorkspaceApiService.verifyClientConfig(this.props.workspaceUrl); + await workspaceApi.verifyClientConfig(this.props.workspaceUrl); this.setState({ clientConfigOOD: false, updating: false }); } catch (err: any) { // catch 409 error - if (ApiErrorHandler.isHttpError(err, 409)) { + if (apiErrorHandler.isHttpError(err, 409)) { this.setState({ invalidText: `The client config in this workspace is out of date. Please refresh it first.`, clientConfigOOD: true, @@ -613,7 +613,7 @@ class WSEditorExportDialog extends React.Component { this.setState({ updating: true }); try { - await WorkspaceApiService.inheritClientConfig(this.props.workspaceUrl); + await workspaceApi.inheritClientConfig(this.props.workspaceUrl); this.setState({ clientConfigOOD: false, updating: false }); this.props.onClose(false, true); } catch (err: any) { console.error(err); this.setState({ - invalidText: ApiErrorHandler.getErrorMessage(err), + invalidText: apiErrorHandler.getErrorMessage(err), updating: false, }); } @@ -639,13 +639,13 @@ class WSEditorExportDialog extends React.Component { setUpdating(true); - WorkspaceApiService.deleteWorkspace(props.workspaceName) + workspaceApi.deleteWorkspace(props.workspaceName) .then(() => { setUpdating(false); props.onClose(true); }) .catch((err: any) => { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); }); }; @@ -789,7 +789,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< updating: true, }); try { - const resources: Resource[] = await WorkspaceApiService.getWorkspaceResources(this.props.workspaceUrl); + const resources: Resource[] = await workspaceApi.getWorkspaceResources(this.props.workspaceUrl); this.setState({ updating: false, resourceOptions: resources, @@ -798,7 +798,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< } catch (err: any) { console.error(err); this.setState({ - invalidText: ApiErrorHandler.getErrorMessage(err), + invalidText: apiErrorHandler.getErrorMessage(err), updating: false, }); } @@ -833,7 +833,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< try { if (this.props.source.toLowerCase() === "typespec") { - const swaggerDefault = await WorkspaceApiService.getWorkspaceSwaggerDefault(this.props.workspaceName); + const swaggerDefault = await workspaceApi.getWorkspaceSwaggerDefault(this.props.workspaceName); const { modNames, rpName, source } = swaggerDefault; if (!modNames || modNames.length === 0 || !rpName || !source || source.toLowerCase() !== "typespec") { this.setState({ @@ -864,9 +864,9 @@ class WSEditorSwaggerReloadDialog extends React.Component< return; } data.resources = emitterOptionRes; - await WorkspaceApiService.reloadTypespecResources(this.props.workspaceUrl, data); + await workspaceApi.reloadTypespecResources(this.props.workspaceUrl, data); } else { - await WorkspaceApiService.reloadSwaggerResources(this.props.workspaceUrl, data); + await workspaceApi.reloadSwaggerResources(this.props.workspaceUrl, data); } this.setState({ @@ -876,7 +876,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< } catch (err: any) { console.error(err); this.setState({ - invalidText: ApiErrorHandler.getErrorMessage(err), + invalidText: apiErrorHandler.getErrorMessage(err), updating: false, }); } @@ -1080,7 +1080,7 @@ class WSRenameDialog extends React.Component { this.setState({ updating: false, @@ -1090,7 +1090,7 @@ class WSRenameDialog extends React.Component { this.setState({ updating: false, - invalidText: ApiErrorHandler.getErrorMessage(err), + invalidText: apiErrorHandler.getErrorMessage(err), }); }); } @@ -1156,3 +1156,5 @@ const WSEditorWrapper = (props: any) => { }; export { WSEditorWrapper as WSEditor }; + + diff --git a/src/web/src/views/workspace/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index f8b7757b..6cbfee81 100644 --- a/src/web/src/views/workspace/WSEditorClientConfig.tsx +++ b/src/web/src/views/workspace/WSEditorClientConfig.tsx @@ -19,7 +19,7 @@ import { Tabs, Tab, } from "@mui/material"; -import { WorkspaceApiService, SpecsApiService, ApiErrorHandler } from "../../services"; +import { workspaceApi, specsApi, apiErrorHandler } from "../../services"; import DoDisturbOnRoundedIcon from "@mui/icons-material/DoDisturbOnRounded"; import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import { Plane, Resource } from "./WSEditorCommandContent"; @@ -163,7 +163,7 @@ class WSEditorClientConfigDialog extends React.Component< updating: true, }); - const planes = await SpecsApiService.getPlanes(); + const planes = await specsApi.getPlanes(); const planeOptions: string[] = planes.map((v: any) => v.displayName); this.setState({ planes: planes, @@ -173,7 +173,7 @@ class WSEditorClientConfigDialog extends React.Component< await this.onPlaneSelectorUpdate(planeOptions[0]); } catch (err: any) { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -211,7 +211,7 @@ class WSEditorClientConfigDialog extends React.Component< this.setState({ updating: true, }); - const options = await SpecsApiService.getSwaggerModules(plane!.name); + const options = await specsApi.getSwaggerModules(plane!.name); this.setState((preState) => { const planes = preState.planes; const index = planes.findIndex((v) => v.name === plane!.name); @@ -227,7 +227,7 @@ class WSEditorClientConfigDialog extends React.Component< await this.onModuleSelectionUpdate(null); } catch (err: any) { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -262,7 +262,7 @@ class WSEditorClientConfigDialog extends React.Component< this.setState({ updating: true, }); - const options = await SpecsApiService.getResourceProviders(moduleUrl); + const options = await specsApi.getResourceProviders(moduleUrl); const selectedResourceProvider = options.length === 1 ? options[0] : null; this.setState({ updating: false, @@ -272,7 +272,7 @@ class WSEditorClientConfigDialog extends React.Component< this.onResourceProviderUpdate(selectedResourceProvider); } catch (err: any) { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -307,7 +307,7 @@ class WSEditorClientConfigDialog extends React.Component< updating: true, }); try { - const resources = await SpecsApiService.getProviderResources(resourceProviderUrl); + const resources = await specsApi.getProviderResources(resourceProviderUrl); const versionResourceIdMap: SwaggerVersionResourceIdMap = {}; const versionOptions: string[] = []; const resourceIdList: string[] = []; @@ -350,7 +350,7 @@ class WSEditorClientConfigDialog extends React.Component< this.onVersionUpdate(selectVersion); } catch (err: any) { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}`, }); @@ -388,7 +388,7 @@ class WSEditorClientConfigDialog extends React.Component< loadWorkspaceClientConfig = async () => { this.setState({ updating: true }); try { - const clientConfigData = await WorkspaceApiService.getClientConfig(this.props.workspaceUrl); + const clientConfigData = await workspaceApi.getClientConfig(this.props.workspaceUrl); const clientConfig: ClientConfig = { version: clientConfigData.version, auth: clientConfigData.auth, @@ -454,13 +454,13 @@ class WSEditorClientConfigDialog extends React.Component< }); } catch (err: any) { // catch 404 error - if (ApiErrorHandler.isHttpError(err, 404)) { + if (apiErrorHandler.isHttpError(err, 404)) { this.setState({ isAdd: true, }); } else { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}` }); } } @@ -638,7 +638,7 @@ class WSEditorClientConfigDialog extends React.Component< ) => { this.setState({ updating: true }); try { - await WorkspaceApiService.updateClientConfig(this.props.workspaceUrl, { + await workspaceApi.updateClientConfig(this.props.workspaceUrl, { templates: templates, cloudMetadata: cloudMetadata, resource: resource, @@ -648,7 +648,7 @@ class WSEditorClientConfigDialog extends React.Component< this.props.onClose(true); } catch (err: any) { console.error(err); - const message = ApiErrorHandler.getErrorMessage(err); + const message = apiErrorHandler.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}` }); this.setState({ updating: false }); } diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index a5dbef79..805714df 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -27,7 +27,7 @@ import CallSplitSharpIcon from "@mui/icons-material/CallSplitSharp"; import EditIcon from "@mui/icons-material/Edit"; import ImportExportIcon from "@mui/icons-material/ImportExport"; -import { CommandApiService, ApiErrorHandler } from "../../services"; +import { commandApi, apiErrorHandler } from "../../services"; import pluralize from "pluralize"; import React, { useEffect, useState } from "react"; import WSECArgumentSimilarPicker, { ArgSimilarTree, BuildArgSimilarTree } from "./argument/WSECArgumentSimilarPicker"; @@ -920,12 +920,12 @@ function ArgumentDialog(props: { const argumentUrl = `${props.commandUrl}/Arguments/${props.arg.var}`; try { - await CommandApiService.updateCommandArgument(argumentUrl, data); + await commandApi.updateCommandArgument(argumentUrl, data); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -938,7 +938,7 @@ function ArgumentDialog(props: { setUpdating(true); try { - const res = await CommandApiService.findSimilarArguments(props.commandUrl, props.arg.var); + const res = await commandApi.findSimilarArguments(props.commandUrl, props.arg.var); setUpdating(false); const { tree, expandedIds } = BuildArgSimilarTree(res); setArgSimilarTree(tree); @@ -946,7 +946,7 @@ function ArgumentDialog(props: { setArgSimilarTreeArgIdsUpdated([]); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -977,12 +977,12 @@ function ArgumentDialog(props: { const argId = argSimilarTree!.selectedArgIds[idx]; if (updatedIds.indexOf(argId) === -1) { try { - await CommandApiService.updateArgumentById(argId, data); + await commandApi.updateArgumentById(argId, data); updatedIds.push(argId); setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - invalidText += ApiErrorHandler.getErrorMessage(err); + invalidText += apiErrorHandler.getErrorMessage(err); } } } @@ -1453,12 +1453,12 @@ function FlattenDialog(props: { const flattenUrl = `${props.commandUrl}/Arguments/${props.arg.var}/Flatten`; try { - await CommandApiService.flattenArgument(flattenUrl, data); + await commandApi.flattenArgument(flattenUrl, data); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -1471,7 +1471,7 @@ function FlattenDialog(props: { setUpdating(true); try { - const res = await CommandApiService.findSimilarArguments(props.commandUrl, props.arg.var); + const res = await commandApi.findSimilarArguments(props.commandUrl, props.arg.var); setUpdating(false); const { tree, expandedIds } = BuildArgSimilarTree(res); setArgSimilarTree(tree); @@ -1479,7 +1479,7 @@ function FlattenDialog(props: { setArgSimilarTreeArgIdsUpdated([]); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -1510,12 +1510,12 @@ function FlattenDialog(props: { if (updatedIds.indexOf(argId) === -1) { const flattenUrl = `${argId}/Flatten`; try { - await CommandApiService.flattenArgument(flattenUrl, data); + await commandApi.flattenArgument(flattenUrl, data); updatedIds.push(argId); setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - invalidText += ApiErrorHandler.getErrorMessage(err); + invalidText += apiErrorHandler.getErrorMessage(err); } } } @@ -1654,12 +1654,12 @@ function UnwrapClsDialog(props: { const flattenUrl = `${props.commandUrl}/Arguments/${argVar}/UnwrapClass`; try { - await CommandApiService.unwrapClassArgument(flattenUrl); + await commandApi.unwrapClassArgument(flattenUrl); setUpdating(false); await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(ApiErrorHandler.getErrorMessage(err)); + setInvalidText(apiErrorHandler.getErrorMessage(err)); setUpdating(false); } }; @@ -2625,3 +2625,5 @@ const DecodeArgs = (argGroups: any[]): { args: CMDArg[]; clsArgDefineMap: ClsArg export default WSEditorCommandArgumentsContent; export { DecodeArgs }; export type { ClsArgDefinitionMap, CMDArg }; + + diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index f3c3ec44..1470634a 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -47,7 +47,7 @@ import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; import DataObjectIcon from "@mui/icons-material/DataObject"; import LabelIcon from "@mui/icons-material/Label"; -import { CommandApiService, ApiErrorHandler } from "../../services"; +import { commandApi, apiErrorHandler } from "../../services"; import WSEditorCommandArgumentsContent, { ClsArgDefinitionMap, CMDArg, @@ -268,7 +268,7 @@ class WSEditorCommandContent extends React.Component { - return await CommandApiService.getCommandsForResource(url); + return await commandApi.getCommandsForResource(url); }); Promise.all(promisesAll) .then((responses) => { @@ -813,7 +813,7 @@ function CommandDeleteDialog(props: { setUpdating(true); const urls = getUrls(); const promisesAll = urls.map(async (url) => { - return await CommandApiService.deleteResource(url); + return await commandApi.deleteResource(url); }); Promise.all(promisesAll) .then(() => { @@ -937,7 +937,7 @@ class CommandDialog extends React.Component { } export { ExampleItemSelector }; + + diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index b3c4257f..526adccc 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -27,7 +27,7 @@ import { FormHelperText, } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; -import { WorkspaceApiService, SpecsApiService, ApiErrorHandler } from "../../services"; +import { workspaceApi, specsApi, apiErrorHandler } from "../../services"; import EditorPageLayout from "../../components/EditorPageLayout"; import { styled } from "@mui/material/styles"; import { getTypespecRPResources, getTypespecRPResourcesOperations } from "../../typespec"; @@ -158,7 +158,7 @@ class WSEditorSwaggerPicker extends React.Component { await this.loadSwaggerModules(this.props.plane); try { - const swaggerDefault = await WorkspaceApiService.getSwaggerDefault(this.props.workspaceName); + const swaggerDefault = await workspaceApi.getSwaggerDefault(this.props.workspaceName); // default module name if (swaggerDefault.modNames === null || swaggerDefault.modNames.length == 0) { return; @@ -183,7 +183,7 @@ class WSEditorSwaggerPicker extends React.Component { try { - const options = await SpecsApiService.getSwaggerModules(plane); + const options = await specsApi.getSwaggerModules(plane); this.setState((preState) => { return { ...preState, @@ -212,7 +212,7 @@ class WSEditorSwaggerPicker extends React.Component v === preferredRP) >= 0) { @@ -239,7 +239,7 @@ class WSEditorSwaggerPicker extends React.Component { try { - const resources = await WorkspaceApiService.getWorkspaceResourcesByName(this.props.workspaceName); + const resources = await workspaceApi.getWorkspaceResourcesByName(this.props.workspaceName); const existingResources = new Set(); if (resources && Array.isArray(resources) && resources.length > 0) { resources.forEach((resource: any) => { @@ -266,7 +266,7 @@ class WSEditorSwaggerPicker extends React.Component { if (aazResource.versions) { resourceMap[aazResource.id].aazVersions = aazResource.versions; @@ -343,7 +343,7 @@ class WSEditorSwaggerPicker extends React.Component { export default WSEditorSwaggerPicker; export { SwaggerItemSelector }; + + diff --git a/src/web/src/views/workspace/WSEditorTheme.tsx b/src/web/src/views/workspace/WSEditorTheme.tsx index cde087ee..e27b6aad 100644 --- a/src/web/src/views/workspace/WSEditorTheme.tsx +++ b/src/web/src/views/workspace/WSEditorTheme.tsx @@ -81,3 +81,5 @@ export { SmallPreviewTypography, SmallExperimentalTypography, }; + + diff --git a/src/web/src/views/workspace/WSEditorToolBar.tsx b/src/web/src/views/workspace/WSEditorToolBar.tsx index baba8b8d..cdedf1a2 100644 --- a/src/web/src/views/workspace/WSEditorToolBar.tsx +++ b/src/web/src/views/workspace/WSEditorToolBar.tsx @@ -81,3 +81,5 @@ class WSEditorToolBar extends React.Component { } export default WSEditorToolBar; + + diff --git a/src/web/src/views/workspace/WorkspaceInstruction.tsx b/src/web/src/views/workspace/WorkspaceInstruction.tsx index aabd9c7c..629089f8 100644 --- a/src/web/src/views/workspace/WorkspaceInstruction.tsx +++ b/src/web/src/views/workspace/WorkspaceInstruction.tsx @@ -49,3 +49,5 @@ class WorkspaceInstruction extends React.Component { } export default WorkspaceInstruction; + + diff --git a/src/web/src/views/workspace/WorkspacePage.tsx b/src/web/src/views/workspace/WorkspacePage.tsx index 509025a4..57ac1de4 100644 --- a/src/web/src/views/workspace/WorkspacePage.tsx +++ b/src/web/src/views/workspace/WorkspacePage.tsx @@ -13,3 +13,5 @@ class WorkspacePage extends React.Component { } export default withRoot(WorkspacePage); + + diff --git a/src/web/src/views/workspace/WorkspaceSelector.tsx b/src/web/src/views/workspace/WorkspaceSelector.tsx index fe6902c9..ac31a904 100644 --- a/src/web/src/views/workspace/WorkspaceSelector.tsx +++ b/src/web/src/views/workspace/WorkspaceSelector.tsx @@ -15,7 +15,7 @@ import * as React from "react"; import { SwaggerItemSelector } from "./WSEditorSwaggerPicker"; import styled from "@emotion/styled"; import { Plane } from "./WSEditorCommandContent"; -import { WorkspaceApiService, SpecsApiService, ApiErrorHandler, type Workspace as WorkspaceType } from "../../services"; +import { workspaceApi, specsApi, apiErrorHandler, type Workspace as WorkspaceType } from "../../services"; interface WorkspaceSelectorProps { name: string; @@ -52,7 +52,7 @@ class WorkspaceSelector extends React.Component { try { - const options = await WorkspaceApiService.getWorkspaces(); + const options = await workspaceApi.getWorkspaces(); this.setState({ options: options, }); @@ -216,7 +216,7 @@ class WorkspaceCreateDialog extends React.Component v.displayName); this.setState({ planes: planes, @@ -228,7 +228,7 @@ class WorkspaceCreateDialog extends React.Component { const planes = preState.planes; const index = planes.findIndex((v) => v.name === plane!.name); @@ -281,7 +281,7 @@ class WorkspaceCreateDialog extends React.Component ({ })); export default WorkspaceSelector; + + diff --git a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx index 677111ee..7b1ffb67 100644 --- a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx +++ b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx @@ -376,3 +376,5 @@ function WSECArgumentSimilarPicker(props: { export default WSECArgumentSimilarPicker; export { BuildArgSimilarTree }; export type { ArgSimilarTree, ArgSimilarGroup, ArgSimilarCommand, ArgSimilarArg }; + + From b301a84deee3e57a77de36ae6b373ec45da993c8 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 15:17:51 +1000 Subject: [PATCH 10/18] refactor: remove empty lines at end of file due to script --- src/web/src/services/workspaceApi.ts | 2 +- src/web/src/views/cli/CLIInstruction.tsx | 1 - src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx | 1 - src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx | 1 - src/web/src/views/cli/CLIModGeneratorToolBar.tsx | 1 - src/web/src/views/cli/CLIModuleCommon.tsx | 1 - src/web/src/views/cli/CLIModuleGenerator.tsx | 1 - src/web/src/views/cli/CLIModuleSelector.tsx | 1 - src/web/src/views/cli/CLIPage.tsx | 1 - src/web/src/views/commands/CommandsPage.tsx | 1 - src/web/src/views/home/HomePage.tsx | 1 - src/web/src/views/workspace/WSEditor.tsx | 1 - src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx | 1 - src/web/src/views/workspace/WSEditorCommandContent.tsx | 1 - src/web/src/views/workspace/WSEditorCommandGroupContent.tsx | 1 - src/web/src/views/workspace/WSEditorCommandTree.tsx | 1 - src/web/src/views/workspace/WSEditorExamplePicker.tsx | 1 - src/web/src/views/workspace/WSEditorSwaggerPicker.tsx | 1 - src/web/src/views/workspace/WSEditorTheme.tsx | 1 - src/web/src/views/workspace/WSEditorToolBar.tsx | 1 - src/web/src/views/workspace/WorkspaceInstruction.tsx | 1 - src/web/src/views/workspace/WorkspacePage.tsx | 1 - src/web/src/views/workspace/WorkspaceSelector.tsx | 1 - .../src/views/workspace/argument/WSECArgumentSimilarPicker.tsx | 1 - 24 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts index cf5eff56..add36488 100644 --- a/src/web/src/services/workspaceApi.ts +++ b/src/web/src/services/workspaceApi.ts @@ -155,4 +155,4 @@ export const workspaceApi = { const res = await axios.get(`${workspaceUrl}/ClientConfig`); return res.data; }, -} as const; +} as const; \ No newline at end of file diff --git a/src/web/src/views/cli/CLIInstruction.tsx b/src/web/src/views/cli/CLIInstruction.tsx index 0e7b850c..6582b29d 100644 --- a/src/web/src/views/cli/CLIInstruction.tsx +++ b/src/web/src/views/cli/CLIInstruction.tsx @@ -68,4 +68,3 @@ class CLIInstruction extends React.Component { export default CLIInstruction; - diff --git a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx index f2252087..76b75b0f 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx @@ -974,4 +974,3 @@ export type { ProfileCommandTree }; export { InitializeCommandTreeByModView, BuildProfileCommandTree, ExportModViewProfile }; - diff --git a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx index dae6a06d..12413150 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx @@ -40,4 +40,3 @@ class CLIModGeneratorProfileTabs extends React.Component { export { WSEditorWrapper as WSEditor }; - diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index 805714df..b0c364a8 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -2626,4 +2626,3 @@ export default WSEditorCommandArgumentsContent; export { DecodeArgs }; export type { ClsArgDefinitionMap, CMDArg }; - diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 1470634a..a3ed4c63 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -1995,4 +1995,3 @@ export { DecodeResponseCommand }; export type { Plane, Command, Resource, ResponseCommand, ResponseCommands, Example }; - diff --git a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx index 470e502c..03a45f16 100644 --- a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx @@ -518,4 +518,3 @@ export default WSEditorCommandGroupContent; export { DecodeResponseCommandGroup }; export type { CommandGroup, ResponseCommandGroup, ResponseCommandGroups }; - diff --git a/src/web/src/views/workspace/WSEditorCommandTree.tsx b/src/web/src/views/workspace/WSEditorCommandTree.tsx index 7a0809cc..7724014c 100644 --- a/src/web/src/views/workspace/WSEditorCommandTree.tsx +++ b/src/web/src/views/workspace/WSEditorCommandTree.tsx @@ -208,4 +208,3 @@ export default WSEditorCommandTree; export type { CommandTreeNode, CommandTreeLeaf }; - diff --git a/src/web/src/views/workspace/WSEditorExamplePicker.tsx b/src/web/src/views/workspace/WSEditorExamplePicker.tsx index 5be344c6..598d099d 100644 --- a/src/web/src/views/workspace/WSEditorExamplePicker.tsx +++ b/src/web/src/views/workspace/WSEditorExamplePicker.tsx @@ -56,4 +56,3 @@ class ExampleItemSelector extends React.Component { export { ExampleItemSelector }; - diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index 526adccc..b5739c73 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -956,4 +956,3 @@ class SwaggerItemSelector extends React.Component { export default WSEditorSwaggerPicker; export { SwaggerItemSelector }; - diff --git a/src/web/src/views/workspace/WSEditorTheme.tsx b/src/web/src/views/workspace/WSEditorTheme.tsx index e27b6aad..7991947a 100644 --- a/src/web/src/views/workspace/WSEditorTheme.tsx +++ b/src/web/src/views/workspace/WSEditorTheme.tsx @@ -82,4 +82,3 @@ export { SmallExperimentalTypography, }; - diff --git a/src/web/src/views/workspace/WSEditorToolBar.tsx b/src/web/src/views/workspace/WSEditorToolBar.tsx index cdedf1a2..a2e20e66 100644 --- a/src/web/src/views/workspace/WSEditorToolBar.tsx +++ b/src/web/src/views/workspace/WSEditorToolBar.tsx @@ -82,4 +82,3 @@ class WSEditorToolBar extends React.Component { export default WSEditorToolBar; - diff --git a/src/web/src/views/workspace/WorkspaceInstruction.tsx b/src/web/src/views/workspace/WorkspaceInstruction.tsx index 629089f8..06c70476 100644 --- a/src/web/src/views/workspace/WorkspaceInstruction.tsx +++ b/src/web/src/views/workspace/WorkspaceInstruction.tsx @@ -50,4 +50,3 @@ class WorkspaceInstruction extends React.Component { export default WorkspaceInstruction; - diff --git a/src/web/src/views/workspace/WorkspacePage.tsx b/src/web/src/views/workspace/WorkspacePage.tsx index 57ac1de4..e9b69611 100644 --- a/src/web/src/views/workspace/WorkspacePage.tsx +++ b/src/web/src/views/workspace/WorkspacePage.tsx @@ -14,4 +14,3 @@ class WorkspacePage extends React.Component { export default withRoot(WorkspacePage); - diff --git a/src/web/src/views/workspace/WorkspaceSelector.tsx b/src/web/src/views/workspace/WorkspaceSelector.tsx index ac31a904..eaf2466e 100644 --- a/src/web/src/views/workspace/WorkspaceSelector.tsx +++ b/src/web/src/views/workspace/WorkspaceSelector.tsx @@ -503,4 +503,3 @@ const MiddlePadding = styled(Box)(() => ({ export default WorkspaceSelector; - diff --git a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx index 7b1ffb67..3520725a 100644 --- a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx +++ b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx @@ -377,4 +377,3 @@ export default WSECArgumentSimilarPicker; export { BuildArgSimilarTree }; export type { ArgSimilarTree, ArgSimilarGroup, ArgSimilarCommand, ArgSimilarArg }; - From 8e590426072b3ba205081d7ae195f94b4e3d71fd Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 15:22:21 +1000 Subject: [PATCH 11/18] refactor: remove singular empty line at EOF --- src/web/src/services/cliApi.ts | 2 +- src/web/src/services/commandApi.ts | 2 +- src/web/src/services/index.ts | 2 +- src/web/src/services/specsApi.ts | 2 +- src/web/src/views/cli/CLIModuleGenerator.tsx | 3 +-- src/web/src/views/cli/CLIModuleSelector.tsx | 3 +-- src/web/src/views/workspace/WSEditor.tsx | 3 +-- src/web/src/views/workspace/WSEditorClientConfig.tsx | 2 +- .../src/views/workspace/WSEditorCommandArgumentsContent.tsx | 3 +-- src/web/src/views/workspace/WSEditorCommandContent.tsx | 3 +-- src/web/src/views/workspace/WSEditorCommandGroupContent.tsx | 3 +-- src/web/src/views/workspace/WSEditorSwaggerPicker.tsx | 3 +-- src/web/src/views/workspace/WorkspaceSelector.tsx | 3 +-- 13 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts index 18cfd6cb..22f8b46e 100644 --- a/src/web/src/services/cliApi.ts +++ b/src/web/src/services/cliApi.ts @@ -46,4 +46,4 @@ export const cliApi = { patchCliModule: async (repoName: string, moduleName: string, data: any): Promise => { await axios.patch(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); }, -} as const; +} as const; \ No newline at end of file diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index b5fdef48..0f59e59c 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -126,4 +126,4 @@ export const apiErrorHandler = { const message = apiErrorHandler.getErrorMessage(err); throw new Error(message); }, -} as const; +} as const; \ No newline at end of file diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 7f353399..49234875 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,4 +1,4 @@ export { workspaceApi, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { specsApi, specsHelper, type Plane, type Resource } from "./specsApi"; export { commandApi, apiErrorHandler } from "./commandApi"; -export { cliApi } from "./cliApi"; +export { cliApi } from "./cliApi"; \ No newline at end of file diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts index 9d577552..d06b3ce7 100644 --- a/src/web/src/services/specsApi.ts +++ b/src/web/src/services/specsApi.ts @@ -83,4 +83,4 @@ export const specsHelper = { const suffix = source.toLowerCase() === "typespec" ? "/TypeSpec" : ""; return `${basePath}${resourceProviderPath}${suffix}`; }, -} as const; +} as const; \ No newline at end of file diff --git a/src/web/src/views/cli/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index 7e91179c..741c75f7 100644 --- a/src/web/src/views/cli/CLIModuleGenerator.tsx +++ b/src/web/src/views/cli/CLIModuleGenerator.tsx @@ -397,5 +397,4 @@ export type { CLISpecsSimpleCommandGroup, CLISpecsSimpleCommand, }; -export { CLIModuleGeneratorWrapper as CLIModuleGenerator }; - +export { CLIModuleGeneratorWrapper as CLIModuleGenerator }; \ No newline at end of file diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index ac87b30c..b37a26b4 100644 --- a/src/web/src/views/cli/CLIModuleSelector.tsx +++ b/src/web/src/views/cli/CLIModuleSelector.tsx @@ -230,5 +230,4 @@ class CLIModuleSelector extends React.Component { return ; }; -export { WSEditorWrapper as WSEditor }; - +export { WSEditorWrapper as WSEditor }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index 6cbfee81..1f34a58b 100644 --- a/src/web/src/views/workspace/WSEditorClientConfig.tsx +++ b/src/web/src/views/workspace/WSEditorClientConfig.tsx @@ -1050,4 +1050,4 @@ type ResourceVersionOperations = { }; export default WSEditorClientConfigDialog; -export type { ClientEndpointTemplate, ClientTemplateMap, ClientAADAuth, ClientConfig }; +export type { ClientEndpointTemplate, ClientTemplateMap, ClientAADAuth, ClientConfig }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index b0c364a8..fb39e1bc 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -2624,5 +2624,4 @@ const DecodeArgs = (argGroups: any[]): { args: CMDArg[]; clsArgDefineMap: ClsArg export default WSEditorCommandArgumentsContent; export { DecodeArgs }; -export type { ClsArgDefinitionMap, CMDArg }; - +export type { ClsArgDefinitionMap, CMDArg }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index a3ed4c63..6719aa1f 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -1993,5 +1993,4 @@ export default WSEditorCommandContent; export { DecodeResponseCommand }; -export type { Plane, Command, Resource, ResponseCommand, ResponseCommands, Example }; - +export type { Plane, Command, Resource, ResponseCommand, ResponseCommands, Example }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx index 03a45f16..e5e6531f 100644 --- a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx @@ -516,5 +516,4 @@ const DecodeResponseCommandGroup = (commandGroup: ResponseCommandGroup): Command export default WSEditorCommandGroupContent; export { DecodeResponseCommandGroup }; -export type { CommandGroup, ResponseCommandGroup, ResponseCommandGroups }; - +export type { CommandGroup, ResponseCommandGroup, ResponseCommandGroups }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index b5739c73..63a14c36 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -954,5 +954,4 @@ class SwaggerItemSelector extends React.Component { } export default WSEditorSwaggerPicker; -export { SwaggerItemSelector }; - +export { SwaggerItemSelector }; \ No newline at end of file diff --git a/src/web/src/views/workspace/WorkspaceSelector.tsx b/src/web/src/views/workspace/WorkspaceSelector.tsx index eaf2466e..15b3e235 100644 --- a/src/web/src/views/workspace/WorkspaceSelector.tsx +++ b/src/web/src/views/workspace/WorkspaceSelector.tsx @@ -501,5 +501,4 @@ const MiddlePadding = styled(Box)(() => ({ height: "1.5vh", })); -export default WorkspaceSelector; - +export default WorkspaceSelector; \ No newline at end of file From 92e7f43ca68816a4b4c3bc1a612f08f881b74328 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 15:24:25 +1000 Subject: [PATCH 12/18] refactor: manually remove empty lines from copilot --- src/web/src/services/workspaceApi.ts | 2 +- src/web/src/views/cli/CLIInstruction.tsx | 1 - src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx | 1 - src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx | 1 - src/web/src/views/cli/CLIModGeneratorToolBar.tsx | 1 - src/web/src/views/cli/CLIModuleCommon.tsx | 1 - src/web/src/views/cli/CLIPage.tsx | 1 - 7 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/web/src/services/workspaceApi.ts b/src/web/src/services/workspaceApi.ts index add36488..cf5eff56 100644 --- a/src/web/src/services/workspaceApi.ts +++ b/src/web/src/services/workspaceApi.ts @@ -155,4 +155,4 @@ export const workspaceApi = { const res = await axios.get(`${workspaceUrl}/ClientConfig`); return res.data; }, -} as const; \ No newline at end of file +} as const; diff --git a/src/web/src/views/cli/CLIInstruction.tsx b/src/web/src/views/cli/CLIInstruction.tsx index 6582b29d..37954e73 100644 --- a/src/web/src/views/cli/CLIInstruction.tsx +++ b/src/web/src/views/cli/CLIInstruction.tsx @@ -67,4 +67,3 @@ class CLIInstruction extends React.Component { } export default CLIInstruction; - diff --git a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx index 76b75b0f..39f2f1b0 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileCommandTree.tsx @@ -973,4 +973,3 @@ export default CLIModGeneratorProfileCommandTree; export type { ProfileCommandTree }; export { InitializeCommandTreeByModView, BuildProfileCommandTree, ExportModViewProfile }; - diff --git a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx index 12413150..a1cd079a 100644 --- a/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx +++ b/src/web/src/views/cli/CLIModGeneratorProfileTabs.tsx @@ -39,4 +39,3 @@ class CLIModGeneratorProfileTabs extends React.Component Date: Mon, 29 Sep 2025 15:28:44 +1000 Subject: [PATCH 13/18] refactor: apply prettier line endings uniformly --- src/web/src/services/cliApi.ts | 2 +- src/web/src/services/commandApi.ts | 2 +- src/web/src/services/index.ts | 2 +- src/web/src/services/specsApi.ts | 2 +- src/web/src/views/cli/CLIModuleGenerator.tsx | 2 +- src/web/src/views/cli/CLIModuleSelector.tsx | 2 +- src/web/src/views/commands/CommandsPage.tsx | 1 - src/web/src/views/home/HomePage.tsx | 1 - src/web/src/views/workspace/WSEditor.tsx | 8 +++++--- src/web/src/views/workspace/WSEditorClientConfig.tsx | 2 +- .../views/workspace/WSEditorCommandArgumentsContent.tsx | 2 +- src/web/src/views/workspace/WSEditorCommandContent.tsx | 2 +- .../src/views/workspace/WSEditorCommandGroupContent.tsx | 2 +- src/web/src/views/workspace/WSEditorCommandTree.tsx | 1 - src/web/src/views/workspace/WSEditorExamplePicker.tsx | 1 - src/web/src/views/workspace/WSEditorSwaggerPicker.tsx | 2 +- src/web/src/views/workspace/WSEditorTheme.tsx | 1 - src/web/src/views/workspace/WSEditorToolBar.tsx | 1 - src/web/src/views/workspace/WorkspaceInstruction.tsx | 1 - src/web/src/views/workspace/WorkspacePage.tsx | 1 - src/web/src/views/workspace/WorkspaceSelector.tsx | 2 +- .../workspace/argument/WSECArgumentSimilarPicker.tsx | 1 - 22 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts index 22f8b46e..18cfd6cb 100644 --- a/src/web/src/services/cliApi.ts +++ b/src/web/src/services/cliApi.ts @@ -46,4 +46,4 @@ export const cliApi = { patchCliModule: async (repoName: string, moduleName: string, data: any): Promise => { await axios.patch(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); }, -} as const; \ No newline at end of file +} as const; diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index 0f59e59c..b5fdef48 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -126,4 +126,4 @@ export const apiErrorHandler = { const message = apiErrorHandler.getErrorMessage(err); throw new Error(message); }, -} as const; \ No newline at end of file +} as const; diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 49234875..7f353399 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,4 +1,4 @@ export { workspaceApi, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { specsApi, specsHelper, type Plane, type Resource } from "./specsApi"; export { commandApi, apiErrorHandler } from "./commandApi"; -export { cliApi } from "./cliApi"; \ No newline at end of file +export { cliApi } from "./cliApi"; diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts index d06b3ce7..9d577552 100644 --- a/src/web/src/services/specsApi.ts +++ b/src/web/src/services/specsApi.ts @@ -83,4 +83,4 @@ export const specsHelper = { const suffix = source.toLowerCase() === "typespec" ? "/TypeSpec" : ""; return `${basePath}${resourceProviderPath}${suffix}`; }, -} as const; \ No newline at end of file +} as const; diff --git a/src/web/src/views/cli/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index 741c75f7..bc46fdc6 100644 --- a/src/web/src/views/cli/CLIModuleGenerator.tsx +++ b/src/web/src/views/cli/CLIModuleGenerator.tsx @@ -397,4 +397,4 @@ export type { CLISpecsSimpleCommandGroup, CLISpecsSimpleCommand, }; -export { CLIModuleGeneratorWrapper as CLIModuleGenerator }; \ No newline at end of file +export { CLIModuleGeneratorWrapper as CLIModuleGenerator }; diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index b37a26b4..9ee5059b 100644 --- a/src/web/src/views/cli/CLIModuleSelector.tsx +++ b/src/web/src/views/cli/CLIModuleSelector.tsx @@ -230,4 +230,4 @@ class CLIModuleSelector extends React.Component { setUpdating(true); - workspaceApi.deleteWorkspace(props.workspaceName) + workspaceApi + .deleteWorkspace(props.workspaceName) .then(() => { setUpdating(false); props.onClose(true); @@ -1080,7 +1081,8 @@ class WSRenameDialog extends React.Component { this.setState({ updating: false, @@ -1155,4 +1157,4 @@ const WSEditorWrapper = (props: any) => { return ; }; -export { WSEditorWrapper as WSEditor }; \ No newline at end of file +export { WSEditorWrapper as WSEditor }; diff --git a/src/web/src/views/workspace/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index 1f34a58b..6cbfee81 100644 --- a/src/web/src/views/workspace/WSEditorClientConfig.tsx +++ b/src/web/src/views/workspace/WSEditorClientConfig.tsx @@ -1050,4 +1050,4 @@ type ResourceVersionOperations = { }; export default WSEditorClientConfigDialog; -export type { ClientEndpointTemplate, ClientTemplateMap, ClientAADAuth, ClientConfig }; \ No newline at end of file +export type { ClientEndpointTemplate, ClientTemplateMap, ClientAADAuth, ClientConfig }; diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index fb39e1bc..3c511ff2 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -2624,4 +2624,4 @@ const DecodeArgs = (argGroups: any[]): { args: CMDArg[]; clsArgDefineMap: ClsArg export default WSEditorCommandArgumentsContent; export { DecodeArgs }; -export type { ClsArgDefinitionMap, CMDArg }; \ No newline at end of file +export type { ClsArgDefinitionMap, CMDArg }; diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 6719aa1f..904a884a 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -1993,4 +1993,4 @@ export default WSEditorCommandContent; export { DecodeResponseCommand }; -export type { Plane, Command, Resource, ResponseCommand, ResponseCommands, Example }; \ No newline at end of file +export type { Plane, Command, Resource, ResponseCommand, ResponseCommands, Example }; diff --git a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx index e5e6531f..3dd679a0 100644 --- a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx @@ -516,4 +516,4 @@ const DecodeResponseCommandGroup = (commandGroup: ResponseCommandGroup): Command export default WSEditorCommandGroupContent; export { DecodeResponseCommandGroup }; -export type { CommandGroup, ResponseCommandGroup, ResponseCommandGroups }; \ No newline at end of file +export type { CommandGroup, ResponseCommandGroup, ResponseCommandGroups }; diff --git a/src/web/src/views/workspace/WSEditorCommandTree.tsx b/src/web/src/views/workspace/WSEditorCommandTree.tsx index 7724014c..1d9ced96 100644 --- a/src/web/src/views/workspace/WSEditorCommandTree.tsx +++ b/src/web/src/views/workspace/WSEditorCommandTree.tsx @@ -207,4 +207,3 @@ class WSEditorCommandTree extends React.Component { } export { ExampleItemSelector }; - diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index 63a14c36..51824f61 100644 --- a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx +++ b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx @@ -954,4 +954,4 @@ class SwaggerItemSelector extends React.Component { } export default WSEditorSwaggerPicker; -export { SwaggerItemSelector }; \ No newline at end of file +export { SwaggerItemSelector }; diff --git a/src/web/src/views/workspace/WSEditorTheme.tsx b/src/web/src/views/workspace/WSEditorTheme.tsx index 7991947a..cde087ee 100644 --- a/src/web/src/views/workspace/WSEditorTheme.tsx +++ b/src/web/src/views/workspace/WSEditorTheme.tsx @@ -81,4 +81,3 @@ export { SmallPreviewTypography, SmallExperimentalTypography, }; - diff --git a/src/web/src/views/workspace/WSEditorToolBar.tsx b/src/web/src/views/workspace/WSEditorToolBar.tsx index a2e20e66..baba8b8d 100644 --- a/src/web/src/views/workspace/WSEditorToolBar.tsx +++ b/src/web/src/views/workspace/WSEditorToolBar.tsx @@ -81,4 +81,3 @@ class WSEditorToolBar extends React.Component { } export default WSEditorToolBar; - diff --git a/src/web/src/views/workspace/WorkspaceInstruction.tsx b/src/web/src/views/workspace/WorkspaceInstruction.tsx index 06c70476..aabd9c7c 100644 --- a/src/web/src/views/workspace/WorkspaceInstruction.tsx +++ b/src/web/src/views/workspace/WorkspaceInstruction.tsx @@ -49,4 +49,3 @@ class WorkspaceInstruction extends React.Component { } export default WorkspaceInstruction; - diff --git a/src/web/src/views/workspace/WorkspacePage.tsx b/src/web/src/views/workspace/WorkspacePage.tsx index e9b69611..509025a4 100644 --- a/src/web/src/views/workspace/WorkspacePage.tsx +++ b/src/web/src/views/workspace/WorkspacePage.tsx @@ -13,4 +13,3 @@ class WorkspacePage extends React.Component { } export default withRoot(WorkspacePage); - diff --git a/src/web/src/views/workspace/WorkspaceSelector.tsx b/src/web/src/views/workspace/WorkspaceSelector.tsx index 15b3e235..f38c324b 100644 --- a/src/web/src/views/workspace/WorkspaceSelector.tsx +++ b/src/web/src/views/workspace/WorkspaceSelector.tsx @@ -501,4 +501,4 @@ const MiddlePadding = styled(Box)(() => ({ height: "1.5vh", })); -export default WorkspaceSelector; \ No newline at end of file +export default WorkspaceSelector; diff --git a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx index 3520725a..677111ee 100644 --- a/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx +++ b/src/web/src/views/workspace/argument/WSECArgumentSimilarPicker.tsx @@ -376,4 +376,3 @@ function WSECArgumentSimilarPicker(props: { export default WSECArgumentSimilarPicker; export { BuildArgSimilarTree }; export type { ArgSimilarTree, ArgSimilarGroup, ArgSimilarCommand, ArgSimilarArg }; - From 156817a33bae59e0a36319ddd2b0936ae93fe49d Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 16:02:25 +1000 Subject: [PATCH 14/18] refactor: errorHandler extracted to own service file --- src/web/src/services/commandApi.ts | 22 +--------------------- src/web/src/services/errorHandler.ts | 20 ++++++++++++++++++++ src/web/src/services/index.ts | 3 ++- 3 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 src/web/src/services/errorHandler.ts diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index b5fdef48..8d371ce0 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { apiErrorHandler } from "./errorHandler"; export const commandApi = { getCommand: async (leafUrl: string): Promise => { @@ -106,24 +107,3 @@ export const commandApi = { } }, } as const; - -export const apiErrorHandler = { - getErrorMessage: (err: any): string => { - if (err.response?.data?.message) { - const data = err.response.data; - const details = data.details ? `: ${JSON.stringify(data.details)}` : ""; - return `ResponseError: ${data.message}${details}`; - } - return "An unexpected error occurred"; - }, - - isHttpError: (err: any, statusCode: number): boolean => { - return err.response?.status === statusCode; - }, - - handleApiError: (err: any, context: string = ""): never => { - console.error(context, err); - const message = apiErrorHandler.getErrorMessage(err); - throw new Error(message); - }, -} as const; diff --git a/src/web/src/services/errorHandler.ts b/src/web/src/services/errorHandler.ts new file mode 100644 index 00000000..fdc063f5 --- /dev/null +++ b/src/web/src/services/errorHandler.ts @@ -0,0 +1,20 @@ +export const apiErrorHandler = { + getErrorMessage: (err: any): string => { + if (err.response?.data?.message) { + const data = err.response.data; + const details = data.details ? `: ${JSON.stringify(data.details)}` : ""; + return `ResponseError: ${data.message}${details}`; + } + return "An unexpected error occurred"; + }, + + isHttpError: (err: any, statusCode: number): boolean => { + return err.response?.status === statusCode; + }, + + handleApiError: (err: any, context: string = ""): never => { + console.error(context, err); + const message = apiErrorHandler.getErrorMessage(err); + throw new Error(message); + }, +} as const; \ No newline at end of file diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 7f353399..67813009 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,4 +1,5 @@ export { workspaceApi, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { specsApi, specsHelper, type Plane, type Resource } from "./specsApi"; -export { commandApi, apiErrorHandler } from "./commandApi"; +export { commandApi } from "./commandApi"; +export { apiErrorHandler } from "./errorHandler"; export { cliApi } from "./cliApi"; From cad5c6312e413427a5129144b9cfd654f33e5ce8 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 16:03:31 +1000 Subject: [PATCH 15/18] refactor: errorHandler changes --- src/web/src/services/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/src/services/errorHandler.ts b/src/web/src/services/errorHandler.ts index fdc063f5..870ad334 100644 --- a/src/web/src/services/errorHandler.ts +++ b/src/web/src/services/errorHandler.ts @@ -17,4 +17,4 @@ export const apiErrorHandler = { const message = apiErrorHandler.getErrorMessage(err); throw new Error(message); }, -} as const; \ No newline at end of file +} as const; From d2bf829f585cef6a2c51ddcf55f310c62d5cbe8f Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 16:12:17 +1000 Subject: [PATCH 16/18] refactor: remove throws from error handling to preserve original behaviour --- src/web/src/services/commandApi.ts | 9 ++------- src/web/src/services/errorHandler.ts | 6 ------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/web/src/services/commandApi.ts b/src/web/src/services/commandApi.ts index 8d371ce0..e458dbb6 100644 --- a/src/web/src/services/commandApi.ts +++ b/src/web/src/services/commandApi.ts @@ -1,5 +1,4 @@ import axios from "axios"; -import { apiErrorHandler } from "./errorHandler"; export const commandApi = { getCommand: async (leafUrl: string): Promise => { @@ -99,11 +98,7 @@ export const commandApi = { arg: string; }, ): Promise => { - try { - const response = await axios.post(subresourceUrl, data); - return response.data; - } catch (err: any) { - apiErrorHandler.handleApiError(err, "Failed to create subresource"); - } + const response = await axios.post(subresourceUrl, data); + return response.data; }, } as const; diff --git a/src/web/src/services/errorHandler.ts b/src/web/src/services/errorHandler.ts index 870ad334..e79847fc 100644 --- a/src/web/src/services/errorHandler.ts +++ b/src/web/src/services/errorHandler.ts @@ -11,10 +11,4 @@ export const apiErrorHandler = { isHttpError: (err: any, statusCode: number): boolean => { return err.response?.status === statusCode; }, - - handleApiError: (err: any, context: string = ""): never => { - console.error(context, err); - const message = apiErrorHandler.getErrorMessage(err); - throw new Error(message); - }, } as const; From b5457f8e6e7119de478f6fbb0c90d56831d1c4b1 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Mon, 29 Sep 2025 16:39:03 +1000 Subject: [PATCH 17/18] refactor: more robust error handling and dont auto-dimiss error modal --- src/web/src/services/errorHandler.ts | 15 +++++++++++++++ src/web/src/views/cli/CLIModuleGenerator.tsx | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/web/src/services/errorHandler.ts b/src/web/src/services/errorHandler.ts index e79847fc..05000775 100644 --- a/src/web/src/services/errorHandler.ts +++ b/src/web/src/services/errorHandler.ts @@ -1,10 +1,25 @@ export const apiErrorHandler = { getErrorMessage: (err: any): string => { + console.log("err: ", err); + if (err.response?.data?.message) { const data = err.response.data; const details = data.details ? `: ${JSON.stringify(data.details)}` : ""; return `ResponseError: ${data.message}${details}`; } + + if (err instanceof Error && err.message) { + return err.message; + } + + if (err && typeof err === "object" && err.message) { + return err.message; + } + + if (typeof err === "string") { + return err; + } + return "An unexpected error occurred"; }, diff --git a/src/web/src/views/cli/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index bc46fdc6..2f75a2d2 100644 --- a/src/web/src/views/cli/CLIModuleGenerator.tsx +++ b/src/web/src/views/cli/CLIModuleGenerator.tsx @@ -164,6 +164,7 @@ const CLIModuleGenerator: React.FC = ({ params }) => { const loadModule = async () => { try { setLoading(true); + setInvalidText(undefined); const profiles = await cliApi.getCliProfiles(); const modView: CLIModView = await cliApi.getCliModule(params.repoName, params.moduleName); const simpleTree: CLISpecsSimpleCommandTree = await cliApi.getSimpleCommandTree(); @@ -189,7 +190,6 @@ const CLIModuleGenerator: React.FC = ({ params }) => { } catch (err: any) { console.error(err); setInvalidText(apiErrorHandler.getErrorMessage(err)); - setLoading(false); } }; From 8b19085c86bdb7260eab6c40a41ae3ab11e7adb8 Mon Sep 17 00:00:00 2001 From: Daniel Languiller Date: Tue, 30 Sep 2025 10:00:45 +1000 Subject: [PATCH 18/18] refactor: standardize errorHandler naming --- .../{errorHandler.ts => errorHandlerApi.ts} | 2 +- src/web/src/services/index.ts | 2 +- src/web/src/views/cli/CLIModuleGenerator.tsx | 8 ++++---- src/web/src/views/cli/CLIModuleSelector.tsx | 6 +++--- src/web/src/views/workspace/WSEditor.tsx | 18 ++++++++--------- .../views/workspace/WSEditorClientConfig.tsx | 16 +++++++-------- .../WSEditorCommandArgumentsContent.tsx | 16 +++++++-------- .../workspace/WSEditorCommandContent.tsx | 12 +++++------ .../workspace/WSEditorCommandGroupContent.tsx | 4 ++-- .../views/workspace/WSEditorSwaggerPicker.tsx | 20 +++++++++---------- .../src/views/workspace/WorkspaceSelector.tsx | 10 +++++----- 11 files changed, 57 insertions(+), 57 deletions(-) rename src/web/src/services/{errorHandler.ts => errorHandlerApi.ts} (95%) diff --git a/src/web/src/services/errorHandler.ts b/src/web/src/services/errorHandlerApi.ts similarity index 95% rename from src/web/src/services/errorHandler.ts rename to src/web/src/services/errorHandlerApi.ts index 05000775..214dbbc3 100644 --- a/src/web/src/services/errorHandler.ts +++ b/src/web/src/services/errorHandlerApi.ts @@ -1,4 +1,4 @@ -export const apiErrorHandler = { +export const errorHandlerApi = { getErrorMessage: (err: any): string => { console.log("err: ", err); diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts index 67813009..3c0cf6eb 100644 --- a/src/web/src/services/index.ts +++ b/src/web/src/services/index.ts @@ -1,5 +1,5 @@ export { workspaceApi, type Workspace, type CreateWorkspaceData, type ClientConfig } from "./workspaceApi"; export { specsApi, specsHelper, type Plane, type Resource } from "./specsApi"; export { commandApi } from "./commandApi"; -export { apiErrorHandler } from "./errorHandler"; +export { errorHandlerApi } from "./errorHandlerApi"; export { cliApi } from "./cliApi"; diff --git a/src/web/src/views/cli/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index 2f75a2d2..24d10a6f 100644 --- a/src/web/src/views/cli/CLIModuleGenerator.tsx +++ b/src/web/src/views/cli/CLIModuleGenerator.tsx @@ -14,7 +14,7 @@ import { Alert, } from "@mui/material"; import { useParams } from "react-router"; -import { cliApi, apiErrorHandler } from "../../services"; +import { cliApi, errorHandlerApi } from "../../services"; import CLIModGeneratorToolBar from "./CLIModGeneratorToolBar"; import CLIModGeneratorProfileCommandTree, { ExportModViewProfile, @@ -189,7 +189,7 @@ const CLIModuleGenerator: React.FC = ({ params }) => { setLoading(false); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); } }; @@ -329,7 +329,7 @@ function GenerateDialog(props: { props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; @@ -351,7 +351,7 @@ function GenerateDialog(props: { props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index 9ee5059b..aa52544b 100644 --- a/src/web/src/views/cli/CLIModuleSelector.tsx +++ b/src/web/src/views/cli/CLIModuleSelector.tsx @@ -9,7 +9,7 @@ import { TextField, Button, } from "@mui/material"; -import { cliApi, apiErrorHandler } from "../../services"; +import { cliApi, errorHandlerApi } from "../../services"; import * as React from "react"; interface CLIModule { @@ -70,7 +70,7 @@ class CLIModuleSelector extends React.Component { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); }); }; @@ -799,7 +799,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< } catch (err: any) { console.error(err); this.setState({ - invalidText: apiErrorHandler.getErrorMessage(err), + invalidText: errorHandlerApi.getErrorMessage(err), updating: false, }); } @@ -877,7 +877,7 @@ class WSEditorSwaggerReloadDialog extends React.Component< } catch (err: any) { console.error(err); this.setState({ - invalidText: apiErrorHandler.getErrorMessage(err), + invalidText: errorHandlerApi.getErrorMessage(err), updating: false, }); } @@ -1092,7 +1092,7 @@ class WSRenameDialog extends React.Component { this.setState({ updating: false, - invalidText: apiErrorHandler.getErrorMessage(err), + invalidText: errorHandlerApi.getErrorMessage(err), }); }); } diff --git a/src/web/src/views/workspace/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index 6cbfee81..e6d2a257 100644 --- a/src/web/src/views/workspace/WSEditorClientConfig.tsx +++ b/src/web/src/views/workspace/WSEditorClientConfig.tsx @@ -19,7 +19,7 @@ import { Tabs, Tab, } from "@mui/material"; -import { workspaceApi, specsApi, apiErrorHandler } from "../../services"; +import { workspaceApi, specsApi, errorHandlerApi } from "../../services"; import DoDisturbOnRoundedIcon from "@mui/icons-material/DoDisturbOnRounded"; import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import { Plane, Resource } from "./WSEditorCommandContent"; @@ -173,7 +173,7 @@ class WSEditorClientConfigDialog extends React.Component< await this.onPlaneSelectorUpdate(planeOptions[0]); } catch (err: any) { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -227,7 +227,7 @@ class WSEditorClientConfigDialog extends React.Component< await this.onModuleSelectionUpdate(null); } catch (err: any) { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -272,7 +272,7 @@ class WSEditorClientConfigDialog extends React.Component< this.onResourceProviderUpdate(selectedResourceProvider); } catch (err: any) { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ updating: false, invalidText: `ResponseError: ${message}`, @@ -350,7 +350,7 @@ class WSEditorClientConfigDialog extends React.Component< this.onVersionUpdate(selectVersion); } catch (err: any) { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}`, }); @@ -454,13 +454,13 @@ class WSEditorClientConfigDialog extends React.Component< }); } catch (err: any) { // catch 404 error - if (apiErrorHandler.isHttpError(err, 404)) { + if (errorHandlerApi.isHttpError(err, 404)) { this.setState({ isAdd: true, }); } else { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}` }); } } @@ -648,7 +648,7 @@ class WSEditorClientConfigDialog extends React.Component< this.props.onClose(true); } catch (err: any) { console.error(err); - const message = apiErrorHandler.getErrorMessage(err); + const message = errorHandlerApi.getErrorMessage(err); this.setState({ invalidText: `ResponseError: ${message}` }); this.setState({ updating: false }); } diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index 3c511ff2..b88c364e 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -27,7 +27,7 @@ import CallSplitSharpIcon from "@mui/icons-material/CallSplitSharp"; import EditIcon from "@mui/icons-material/Edit"; import ImportExportIcon from "@mui/icons-material/ImportExport"; -import { commandApi, apiErrorHandler } from "../../services"; +import { commandApi, errorHandlerApi } from "../../services"; import pluralize from "pluralize"; import React, { useEffect, useState } from "react"; import WSECArgumentSimilarPicker, { ArgSimilarTree, BuildArgSimilarTree } from "./argument/WSECArgumentSimilarPicker"; @@ -925,7 +925,7 @@ function ArgumentDialog(props: { await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; @@ -946,7 +946,7 @@ function ArgumentDialog(props: { setArgSimilarTreeArgIdsUpdated([]); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; @@ -982,7 +982,7 @@ function ArgumentDialog(props: { setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - invalidText += apiErrorHandler.getErrorMessage(err); + invalidText += errorHandlerApi.getErrorMessage(err); } } } @@ -1458,7 +1458,7 @@ function FlattenDialog(props: { await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; @@ -1479,7 +1479,7 @@ function FlattenDialog(props: { setArgSimilarTreeArgIdsUpdated([]); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; @@ -1515,7 +1515,7 @@ function FlattenDialog(props: { setArgSimilarTreeArgIdsUpdated([...updatedIds]); } catch (err: any) { console.error(err); - invalidText += apiErrorHandler.getErrorMessage(err); + invalidText += errorHandlerApi.getErrorMessage(err); } } } @@ -1659,7 +1659,7 @@ function UnwrapClsDialog(props: { await props.onClose(true); } catch (err: any) { console.error(err); - setInvalidText(apiErrorHandler.getErrorMessage(err)); + setInvalidText(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 904a884a..5c794f38 100644 --- a/src/web/src/views/workspace/WSEditorCommandContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandContent.tsx @@ -47,7 +47,7 @@ import AddCircleRoundedIcon from "@mui/icons-material/AddCircleRounded"; import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; import DataObjectIcon from "@mui/icons-material/DataObject"; import LabelIcon from "@mui/icons-material/Label"; -import { commandApi, apiErrorHandler } from "../../services"; +import { commandApi, errorHandlerApi } from "../../services"; import WSEditorCommandArgumentsContent, { ClsArgDefinitionMap, CMDArg, @@ -964,7 +964,7 @@ class CommandDialog extends React.Component