diff --git a/src/web/src/services/cliApi.ts b/src/web/src/services/cliApi.ts new file mode 100644 index 00000000..18cfd6cb --- /dev/null +++ b/src/web/src/services/cliApi.ts @@ -0,0 +1,49 @@ +import axios from "axios"; + +export const cliApi = { + getCliProfiles: async (): Promise => { + const res = await axios.get(`/CLI/Az/Profiles`); + return res.data; + }, + + getCliModules: async (repoName: string): Promise => { + const res = await axios.get(`/CLI/Az/${repoName}/Modules`); + return res.data; + }, + + createCliModule: async (repoName: string, moduleName: string): Promise => { + const res = await axios.post(`/CLI/Az/${repoName}/Modules`, { name: moduleName }); + return res.data; + }, + + getCliModule: async (repoName: string, moduleName: string): Promise => { + const res = await axios.get(`/CLI/Az/${repoName}/Modules/${moduleName}`); + return res.data; + }, + + 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; + }, + + 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; + }, + + getSimpleCommandTree: async (): Promise => { + const res = await axios.get(`/AAZ/Specs/CommandTree/Simple`); + return res.data; + }, + + updateCliModule: async (repoName: string, moduleName: string, data: any): Promise => { + await axios.put(`/CLI/Az/${repoName}/Modules/${moduleName}`, data); + }, + + 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 new file mode 100644 index 00000000..e458dbb6 --- /dev/null +++ b/src/web/src/services/commandApi.ts @@ -0,0 +1,104 @@ +import axios from "axios"; + +export const commandApi = { + getCommand: async (leafUrl: string): Promise => { + const res = await axios.get(leafUrl); + return res.data; + }, + + getCommandsForResource: async (resourceUrl: string): Promise => { + const res = await axios.get(`${resourceUrl}/Commands`); + return res.data; + }, + + deleteResource: async (resourceUrl: string): Promise => { + await axios.delete(resourceUrl); + }, + + updateCommand: async (leafUrl: string, data: any): Promise => { + const res = await axios.patch(leafUrl, data); + return res.data; + }, + + renameCommand: async (leafUrl: string, newName: string): Promise => { + const res = await axios.post(`${leafUrl}/Rename`, { name: newName }); + return res.data; + }, + + updateCommandExamples: async (leafUrl: string, examples: any[]): Promise => { + const res = await axios.patch(leafUrl, { examples }); + return res.data; + }, + + 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, + })); + }, + + addSubcommands: async (resourceUrl: string, data: any): Promise => { + await axios.post(resourceUrl, data); + }, + + updateCommandOutputs: async (leafUrl: string, outputs: any[]): Promise => { + const res = await axios.patch(leafUrl, { outputs }); + return res.data; + }, + + updateCommandArgument: async (argumentUrl: string, data: any): Promise => { + await axios.patch(argumentUrl, data); + }, + + updateArgumentById: async (argId: string, data: any): Promise => { + await axios.patch(argId, data); + }, + + flattenArgument: async (flattenUrl: string, data?: any): Promise => { + if (data) { + await axios.post(flattenUrl, data); + } else { + await axios.post(flattenUrl); + } + }, + + unwrapClassArgument: async (flattenUrl: string): Promise => { + await axios.post(flattenUrl); + }, + + deleteCommandGroup: async (nodeUrl: string): Promise => { + await axios.delete(nodeUrl); + }, + + updateCommandGroup: async ( + nodeUrl: string, + data: { help: { short: string; lines: string[] }; stage: string }, + ): Promise => { + const res = await axios.patch(nodeUrl, data); + return res.data; + }, + + renameCommandGroup: async (nodeUrl: string, name: string): Promise => { + const res = await axios.post(`${nodeUrl}/Rename`, { name }); + return res.data; + }, + + findSimilarArguments: async (commandUrl: string, argVar: string): Promise => { + const similarUrl = `${commandUrl}/Arguments/${argVar}/FindSimilar`; + const res = await axios.post(similarUrl); + return res.data; + }, + + createSubresource: async ( + subresourceUrl: string, + data: { + commandGroupName: string; + refArgsOptions: { [argVar: string]: string[] }; + arg: string; + }, + ): Promise => { + const response = await axios.post(subresourceUrl, data); + return response.data; + }, +} as const; diff --git a/src/web/src/services/errorHandlerApi.ts b/src/web/src/services/errorHandlerApi.ts new file mode 100644 index 00000000..214dbbc3 --- /dev/null +++ b/src/web/src/services/errorHandlerApi.ts @@ -0,0 +1,29 @@ +export const errorHandlerApi = { + 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"; + }, + + isHttpError: (err: any, statusCode: number): boolean => { + return err.response?.status === statusCode; + }, +} as const; diff --git a/src/web/src/services/index.ts b/src/web/src/services/index.ts new file mode 100644 index 00000000..3c0cf6eb --- /dev/null +++ b/src/web/src/services/index.ts @@ -0,0 +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 { errorHandlerApi } from "./errorHandlerApi"; +export { cliApi } from "./cliApi"; diff --git a/src/web/src/services/specsApi.ts b/src/web/src/services/specsApi.ts new file mode 100644 index 00000000..9d577552 --- /dev/null +++ b/src/web/src/services/specsApi.ts @@ -0,0 +1,86 @@ +import axios from "axios"; + +export interface Plane { + name: string; + displayName: string; + moduleOptions?: string[]; +} + +export interface Resource { + id: string; + version: string; +} + +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, + })); + }, + + getPlaneNames: async (): Promise => { + const res = await axios.get(`/AAZ/Specs/Planes`); + return res.data.map((v: any) => v.name); + }, + + getModulesForPlane: async (planeName: string): Promise => { + const res = await axios.get(`/Swagger/Specs/${planeName}`); + return res.data.map((v: any) => v.url); + }, + + getResourceProviders: async (moduleUrl: string): Promise => { + const res = await axios.get(`${moduleUrl}/ResourceProviders`); + return res.data.map((v: any) => v.url); + }, + + getResources: async (resourceProviderUrl: string): Promise => { + const res = await axios.get(`${resourceProviderUrl}/Resources`); + return res.data; + }, + + getSwaggerModules: async (plane: string): Promise => { + const res = await axios.get(`/Swagger/Specs/${plane}`); + return res.data.map((v: any) => v.url); + }, + + 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); + }, + + getProviderResources: async (resourceProviderUrl: string): Promise => { + const res = await axios.get(`${resourceProviderUrl}/Resources`); + return res.data; + }, + + 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 const specsHelper = { + removeCommonPrefix: (options: string[], prefix: string): string[] => { + return options.map((option) => option.replace(prefix, "")); + }, + + isClientConfigurablePlane: async (planeName: string): Promise => { + const planeNames = await specsApi.getPlaneNames(); + return !planeNames.includes(planeName); + }, + + 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 new file mode 100644 index 00000000..cf5eff56 --- /dev/null +++ b/src/web/src/services/workspaceApi.ts @@ -0,0 +1,158 @@ +import axios from "axios"; + +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; +} + +export const workspaceApi = { + getWorkspaces: async (): 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, + })); + }, + + createWorkspace: async (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, + }; + }, + + getWorkspace: async (workspaceUrl: string): Promise => { + const res = await axios.get(workspaceUrl); + return res.data; + }, + + deleteWorkspace: async (workspaceName: string): Promise => { + const nodeUrl = `/AAZ/Editor/Workspaces/${workspaceName}`; + await axios.delete(nodeUrl); + }, + + renameWorkspace: async (workspaceUrl: string, newName: string): Promise<{ name: string }> => { + const res = await axios.post(`${workspaceUrl}/Rename`, { name: newName }); + return res.data; + }, + + getWorkspaceClientConfig: async (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) { + if (err.response?.status === 404) { + return null; + } + throw err; + } + }, + + updateClientConfig: async (workspaceUrl: string, config: any): Promise => { + await axios.post(`${workspaceUrl}/ClientConfig`, config); + }, + + verifyClientConfig: async (workspaceUrl: string): Promise => { + const url = `${workspaceUrl}/ClientConfig/AAZ/Compare`; + await axios.post(url); + }, + + inheritClientConfig: async (workspaceUrl: string): Promise => { + const url = `${workspaceUrl}/ClientConfig/AAZ/Inherit`; + await axios.post(url); + }, + + generateWorkspace: async (workspaceUrl: string): Promise => { + const url = `${workspaceUrl}/Generate`; + await axios.post(url); + }, + + getWorkspaceResources: async (workspaceUrl: string): Promise => { + const res = await axios.get(`${workspaceUrl}/CommandTree/Nodes/aaz/Resources`); + return res.data; + }, + + getWorkspaceSwaggerDefault: async (workspaceName: string): Promise => { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); + return res.data; + }, + + reloadSwaggerResources: async (workspaceUrl: string, data: any): Promise => { + const reloadUrl = `${workspaceUrl}/Resources/ReloadSwagger`; + await axios.post(reloadUrl, data); + }, + + reloadTypespecResources: async (workspaceUrl: string, data: any): Promise => { + const reloadUrl = `${workspaceUrl}/Resources/ReloadTypespec`; + await axios.post(reloadUrl, data); + }, + + getSwaggerDefault: async (workspaceName: string): Promise => { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/SwaggerDefault`); + return res.data; + }, + + getWorkspaceResourcesByName: async (workspaceName: string): Promise => { + const res = await axios.get(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/Resources`); + return res.data; + }, + + addSwaggerResources: async (workspaceName: string, requestBody: any): Promise => { + await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddSwagger`, requestBody); + }, + + addTypespecResources: async (workspaceName: string, requestBody: any): Promise => { + await axios.post(`/AAZ/Editor/Workspaces/${workspaceName}/CommandTree/Nodes/aaz/AddTypespec`, requestBody); + }, + + 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/CLIModuleGenerator.tsx b/src/web/src/views/cli/CLIModuleGenerator.tsx index ef0e96cb..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 axios from "axios"; +import { cliApi, errorHandlerApi } 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 cliApi.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 cliApi.retrieveCommands(namesList); } const useSpecsCommandTree: () => (namesList: string[][]) => Promise = () => { @@ -167,15 +164,10 @@ 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); + setInvalidText(undefined); + 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); @@ -197,12 +189,7 @@ 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(errorHandlerApi.getErrorMessage(err)); } }; @@ -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 cliApi.updateCliModule(props.repoName, props.moduleName, data); + setUpdating(false); + props.onClose(true); + } catch (err: any) { + console.error(err); + setInvalidText(errorHandlerApi.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 cliApi.patchCliModule(props.repoName, props.moduleName, data); + setUpdating(false); + props.onClose(true); + } catch (err: any) { + console.error(err); + setInvalidText(errorHandlerApi.getErrorMessage(err)); + setUpdating(false); + } }; return ( diff --git a/src/web/src/views/cli/CLIModuleSelector.tsx b/src/web/src/views/cli/CLIModuleSelector.tsx index 078336c9..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 axios from "axios"; +import { cliApi, errorHandlerApi } 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 cliApi.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(errorHandlerApi.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 cliApi.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(errorHandlerApi.getErrorMessage(err)); + } } }; diff --git a/src/web/src/views/workspace/WSEditor.tsx b/src/web/src/views/workspace/WSEditor.tsx index 132170ff..ac4bb02f 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 { workspaceApi, specsApi, errorHandlerApi } 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 specsApi.getPlaneNames(); + const workspaceData = await workspaceApi.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 workspaceApi.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 workspaceApi.verifyClientConfig(this.props.workspaceUrl); this.setState({ clientConfigOOD: false, updating: false }); } catch (err: any) { // catch 409 error - if (err.response?.status === 409) { + if (errorHandlerApi.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 workspaceApi.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: errorHandlerApi.getErrorMessage(err), + updating: false, + }); } }; handleExport = async () => { - const url = `${this.props.workspaceUrl}/Generate`; this.setState({ updating: true }); try { - await axios.post(url); + await workspaceApi.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: errorHandlerApi.getErrorMessage(err), + updating: false, + }); } }; @@ -732,19 +694,15 @@ function WSEditorDeleteDialog(props: { workspaceName: string; open: boolean; onC const handleDelete = () => { setUpdating(true); - const nodeUrl = `/AAZ/Editor/Workspaces/` + props.workspaceName; - axios - .delete(nodeUrl) + workspaceApi + .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(errorHandlerApi.getErrorMessage(err)); setUpdating(false); }); }; @@ -832,8 +790,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 workspaceApi.getWorkspaceResources(this.props.workspaceUrl); this.setState({ updating: false, resourceOptions: resources, @@ -841,13 +798,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: errorHandlerApi.getErrorMessage(err), + updating: false, + }); } }; @@ -878,15 +832,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 workspaceApi.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 +845,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 +865,21 @@ class WSEditorSwaggerReloadDialog extends React.Component< return; } data.resources = emitterOptionRes; - // console.log("reload typespec data: ", data); + await workspaceApi.reloadTypespecResources(this.props.workspaceUrl, data); + } else { + await workspaceApi.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: errorHandlerApi.getErrorMessage(err), + updating: false, + }); } }; @@ -1133,26 +1081,19 @@ class WSRenameDialog extends React.Component { + workspaceApi + .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: errorHandlerApi.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/WSEditorClientConfig.tsx b/src/web/src/views/workspace/WSEditorClientConfig.tsx index 1c31dd66..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 axios from "axios"; +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"; @@ -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 specsApi.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 = errorHandlerApi.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 specsApi.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 = errorHandlerApi.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 specsApi.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 = errorHandlerApi.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 specsApi.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 = errorHandlerApi.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 workspaceApi.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 (errorHandlerApi.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 = errorHandlerApi.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 workspaceApi.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 = 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 f1e967e2..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 axios from "axios"; +import { commandApi, errorHandlerApi } 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 commandApi.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(errorHandlerApi.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 commandApi.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(errorHandlerApi.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 commandApi.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 += errorHandlerApi.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 commandApi.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(errorHandlerApi.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 commandApi.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(errorHandlerApi.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 commandApi.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 += errorHandlerApi.getErrorMessage(err); } } } @@ -1686,15 +1654,12 @@ function UnwrapClsDialog(props: { const flattenUrl = `${props.commandUrl}/Arguments/${argVar}/UnwrapClass`; try { - await axios.post(flattenUrl); + await commandApi.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(errorHandlerApi.getErrorMessage(err)); setUpdating(false); } }; diff --git a/src/web/src/views/workspace/WSEditorCommandContent.tsx b/src/web/src/views/workspace/WSEditorCommandContent.tsx index 02997f3c..5c794f38 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 { commandApi, errorHandlerApi } 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 commandApi.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 commandApi.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,38 @@ 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 { + const renamedData = await commandApi.renameCommand(leafUrl, name); + const cmd = DecodeResponseCommand(renamedData); this.setState({ updating: false, }); + this.props.onClose(cmd); + } + } catch (err: any) { + console.error(err); + this.setState({ + invalidText: errorHandlerApi.getErrorMessage(err), + updating: false, }); + } }; handleClose = () => { @@ -1157,7 +1144,7 @@ class ExampleDialog extends React.Component { + onUpdateExamples = async (examples: Example[]) => { const { workspaceUrl, command } = this.props; const leafUrl = @@ -1169,29 +1156,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 commandApi.updateCommandExamples(leafUrl, examples); + const cmd = DecodeResponseCommand(responseData); + this.setState({ + updating: false, + }); + this.props.onClose(cmd); + } catch (err: any) { + console.error(err); + const message = errorHandlerApi.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + updating: false, }); + } }; handleDelete = () => { @@ -1338,16 +1318,7 @@ class ExampleDialog extends React.Component { - return { - name: v.name, - commands: v.commands, - }; - }); + const examples = await commandApi.generateSwaggerExamples(leafUrl); this.setState({ exampleOptions: examples, updating: false, @@ -1357,13 +1328,10 @@ class ExampleDialog extends React.Component { + const handleUpdateOutput = async () => { setInvalidText(undefined); setUpdating(true); @@ -1918,23 +1884,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 commandApi.updateCommandOutputs(leafUrl, outputs); + const cmd = DecodeResponseCommand(responseData); + setUpdating(false); + props.onClose(cmd); + } catch (err: any) { + console.error(err); + const message = errorHandlerApi.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/WSEditorCommandGroupContent.tsx b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx index f247c9f5..2f48f363 100644 --- a/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandGroupContent.tsx @@ -17,7 +17,7 @@ import { TextField, Typography, } from "@mui/material"; -import axios from "axios"; +import { commandApi, errorHandlerApi } from "../../services"; import * as React from "react"; import { ResponseCommands } from "./WSEditorCommandContent"; import { @@ -252,20 +252,18 @@ function CommandGroupDeleteDialog(props: { const handleClose = () => { 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 commandApi.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,37 @@ 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 { + const renameRes = await commandApi.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: errorHandlerApi.getErrorMessage(err), }); + } }; handleClose = () => { diff --git a/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx b/src/web/src/views/workspace/WSEditorSwaggerPicker.tsx index 4584e99c..deee1633 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 { workspaceApi, specsApi, errorHandlerApi } 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 workspaceApi.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 = errorHandlerApi.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 specsApi.getSwaggerModules(plane); this.setState((preState) => { return { ...preState, @@ -215,12 +212,10 @@ class WSEditorSwaggerPicker extends React.Component v.url); + let options = await specsApi.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 workspaceApi.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 specsApi.filterResourcesByPlane(this.props.plane, resourceIdList); + filterData.resources.forEach((aazResource: AAZResource) => { if (aazResource.versions) { resourceMap[aazResource.id].aazVersions = aazResource.versions; } @@ -363,12 +342,9 @@ class WSEditorSwaggerPicker extends React.Component { + addSwagger = async () => { const { selectedResources, selectedVersion, @@ -451,75 +427,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 workspaceApi.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 = errorHandlerApi.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); + } + } catch (err: any) { + this.setState({ + loading: false, + }); + this.props.onClose(true); + const message = errorHandlerApi.getErrorMessage(err); + this.setState({ + invalidText: `ResponseError: ${message}`, + }); + } + } else { + try { + await workspaceApi.addSwaggerResources(this.props.workspaceName, requestBody); + this.setState({ + loading: false, + }); + this.props.onClose(true); + } catch (err: any) { + console.error(err); + const message = errorHandlerApi.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..f5c4f9e9 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 { workspaceApi, specsApi, errorHandlerApi, 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 workspaceApi.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 specsApi.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 specsApi.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 specsApi.getResourceProviders(moduleUrl); const selectedResourceProvider = options.length === 1 ? options[0] : null; this.setState({ loading: false, @@ -358,13 +323,10 @@ class WorkspaceCreateDialog extends React.Component