import * as vscode from 'vscode'; import * as projectmanager from './ProjectManager'; import { v4 as uuidv4 } from 'uuid'; import axios from 'axios'; import * as path from 'path'; /** * @interface DbProjectManager * * @description HTTP DB project manager client class let handling of an Edge.AI project data model stored remotly */ export class DbProjectManager extends projectmanager.IProjectManager { private projectId: string; public _project: any; private deserializedProject: projectmanager.Project | undefined public get project(): projectmanager.Project { if(!this.deserializedProject){ throw new Error("Project not set"); } return this.deserializedProject; } constructor(backendBaseUrl: vscode.Uri){ super(backendBaseUrl); this.projectId = ""; } /** * Inerithed methods */ /** * @name connect * @description Set current project * * @param projectId Project ID */ public connect(projectId: string) { this.uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId); this.projectId = projectId; } /** * @name load * @description Query current selected project from DB */ public async load() { const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId); const response = await axios.get(uri.toString()); this._project = response.data; this._project.inputs = []; this._project.models = []; this._project.outputs = []; this._project.deployments = []; this.deserializedProject = projectmanager.Project.deserialize(this._project, uri); } /** * @name newProject * @description Load project json file from filesystem and fire project change event * * @param projectId Project ID (optional). If not provided, it will be generated following UUID v4 format * @param name Project name * @param description Project description * @param owner Project owner * @param version Project version (optional). If not provided, will be assigned the default version (i.e. "0.0.1") */ public async newProject(id: string, name: string, description: string, owner: string, version: string) { const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects"); const options: Object = { }; const body: Object = { ai_project_name: name, description: description || "", owner: owner, uuid: id, version: version }; await axios.post(uri.toString(), body ,options); } /** * @name addModel * @description Create a new model for the current project * * @param modelId Model ID. If not provided, it will be generated following UUID v4 format * @param inputId Input ID. If not provided, it will be generated following UUID v4 format * @param outputId Output ID. If not provided, it will be generated following UUID v4 format * @param name Model name * @param stage Model stage * @param inputType Model input type * @param modelType Model type * @param outputType Model output type * @param trainingType Model training type * @param modelMetadata Model metadata * @param inputTools Model input tools * @param outputTools Model output tools */ public async addModel(modelId: string, inputId: string, outputId: string, name: string, stage: projectmanager.ModelStage, inputType: projectmanager.InputType, modelType: projectmanager.ModelType, outputType: projectmanager.OutputType, trainingType: projectmanager.TrainingType, modelMetadata: any, inputTools: projectmanager.Tool[], outputTools: projectmanager.Tool[]){ // Create model const modelsUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models"); const newModelBody: Object = { // project_uuid: this.projectId, name: name, uuid: modelId, model_type: modelType.toString(), training_type: trainingType.toString(), stage: stage.toString(), model_metadata: modelMetadata }; await axios.post(modelsUri.toString(), newModelBody ,{}); // Create input const inputsUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "inputs"); const newInputBody: Object = { project_uuid: this.projectId, uuid: inputId, input_type: inputType.toString(), model_ref: modelId, augmentation: [] }; await axios.post(inputsUri.toString(), newInputBody , {}); // Create input tool for(const tool of inputTools){ const inputToolsUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "inputs", inputId, "tools"); const newInputToolBody: Object = { name: tool.name, uuid: uuidv4(), description: (tool).description || "", version: tool.version, container_name: (tool).containerName || "", container_version: (tool).containerVersion || "", parameters: (tool).parameters || {}, }; await axios.post(inputToolsUri.toString(), newInputToolBody , {}); } // Create output const outputsUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs"); const newOutputBody: Object = { // project_uuid: this.projectId, uuid: outputId, output_type: outputType.toString(), model_ref: modelId, output_version: "0.0.1", // best_test: null, events: [], ui: {} }; await axios.post(outputsUri.toString(), newOutputBody , {}); // Create output tool for(const tool of outputTools){ const outputToolsUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId, "tools"); const newOutputToolBody: Object = { name: tool.name, uuid: uuidv4(), description: (tool).description || "", version: tool.version, container_name: (tool).containerName || "", container_version: (tool).containerVersion || "", parameters: (tool).parameters || {}, }; await axios.post(outputToolsUri.toString(), newOutputToolBody , {}); } } /** * @name updateModel * @description Update existing model with input parameters (if provided) * * @param modelId Model ID * @param inputId Input ID (optional). Required only if augmentations is specified * @param stage Model stage (optional). If not provided, field will be not updated * @param modelMetadata Model metadata (optional). If not provided, field will be not updated * @param augmentations Model augmentations (optional). If not provided, field will be not updated */ public async updateModel(modelId: string, inputId: string | undefined, stage: projectmanager.ModelStage | undefined, modelMetadata: any | undefined, augmentations: string[] | undefined){ const modelUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models", modelId); const newModelBody: Object = { stage: stage?.toString(), model_metadata: modelMetadata }; await axios.put(modelUri.toString(), newModelBody ,{}); if(augmentations && inputId){ const inputUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "inputs", inputId); const newInputBody: Object = { augmentation: augmentations }; await axios.put(inputUri.toString(), newInputBody ,{}); } } /** * @name removeModel * @description Remove existing model * * @param modelId Model ID * @param modelId Input ID * @param modelId Output ID */ public async removeModel(modelId: string){ const modelUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models", modelId); await axios.delete(modelUri.toString()); // const inputUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "inputs", inputId); // await axios.delete(inputUri.toString()); // const outputUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId); // await axios.delete(outputUri.toString()); } /** * @name addExperiment * @description Create a new experiment for a given model * * @param modelId Model ID * @param experimentId Experiment ID. If not provided, it will be generated following UUID v4 format * @param name Exeperiment name * @param description Experiment description * @param modelDevFile Model development filename */ public async addExperiment(modelId: string, experimentId: string, name: String, description: String, modelDevFile: String | null) { const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models", modelId, "experiments"); const newExperimentBody: Object = { description: description, name: name, uuid: experimentId, model_uuid: modelId, model_dev_file: modelDevFile }; await axios.post(uri.toString(), newExperimentBody ,{}); } /** * @name updateExperiment * @description Update existing experiment with input parameters (if provided) * * @param modelId Model ID * @param experimentId Experiment ID * @param description Experiment description (optional). If not provided, field will be not updated * @param classes Model development file name (optional). If not provided, field will be not updated */ public async updateExperiment(modelId: string, experimentId: string, description: string | undefined, modelDevFile: string | null | undefined){ const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models", modelId, "experiments", experimentId); const newExperimentBody: Object = { description: description, model_dev_file: modelDevFile }; await axios.post(uri.toString(), newExperimentBody ,{}); } /** * @name removeExperiment * @description Remove existing experiment * * @param modelId Model ID * @param experimentId Experiment ID */ public async removeExperiment(modelId: string, experimenId: string){ const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "models", modelId, "experiments", experimenId); await axios.delete(uri.toString()); } /** * @name addTest * @description Create a new test for a given experiment * * @param modelId Model ID * @param outputId Output ID * @param testId Test ID. If not provided, it will be generated following UUID v4 format * @param name Test name * @param modelFile Model file name */ public async addTest(experimentId: string, outputId: string, testId: string, name: string, modelFile: string | null) { const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId, "tests"); const newOutputBody: Object = { output_uuid: outputId, uuid: testId, name: name, description: name, experiment_ref: experimentId, model_file: modelFile, parameters: {}, reports: [], outputs: [] }; await axios.post(uri.toString(), newOutputBody ,{}); } /** * @name updateTest * @description Update existing test with input parameters (if provided) * * @param modelId Model ID * @param testId Test ID * @param parameters Parameters used to generate reports and/or output. If not provided, field will be not updated * @param reports Report filenames. If not provided, field will be not updated * @param outputs Ouputs filenames. If not provided, field will be not updated * @param isBest Is the best test? (default false) */ public async updateTest(outputId: string, testId: string, parameters: any, reports: string[] | undefined, outputs: string[] | undefined, isBest: boolean | undefined = undefined) { const testUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId, "tests", testId); const newTestBody: Object = { output_uuid: outputId, parameters: parameters, reports: reports, outputs: outputs }; await axios.put(testUri.toString(), newTestBody ,{}); if(isBest === true || isBest == false) { let newOutputBody: any = undefined; const outputUri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId); if(isBest === true) { newOutputBody = {}; newOutputBody.best_test = testId; } else { const response = await axios.get(outputUri.toString()); if(response.data.best_test === testId){ newOutputBody = {}; newOutputBody.best_test = null; } } await axios.put(outputUri.toString(), newTestBody ,{}); } } /** * @name removeTest * @description Remove existing test * * @param modelId Model ID (not required). If provided, it will be ignored * @param outputId Experiment ID * @param testId Test ID */ public async removeTest(modelId: string | undefined, outputId: string, testId: string){ const uri: vscode.Uri = vscode.Uri.joinPath(this.backendBaseUrl, "projects", this.projectId, "outputs", outputId, "tests", testId); await axios.delete(uri.toString()); } }