diff --git a/controller/src/extension.ts b/controller/src/extension.ts index fcd27de..c648131 100644 --- a/controller/src/extension.ts +++ b/controller/src/extension.ts @@ -350,7 +350,7 @@ export function activate(context: vscode.ExtensionContext) { "**/src/vision/sparql/*.sparql" ); let viewpointsFolderWatcher = vscode.workspace.createFileSystemWatcher( - "**/src/vision/viewpoints/*.json" + "**/src/vision/viewpoints/**/*.json" ); let commandsFolderWatcher = vscode.workspace.createFileSystemWatcher( "**/src/vision/commands/*.json" diff --git a/controller/src/interfaces/IPagesSchema.ts b/controller/src/interfaces/IPagesSchema.ts new file mode 100644 index 0000000..3a67082 --- /dev/null +++ b/controller/src/interfaces/IPagesSchema.ts @@ -0,0 +1,31 @@ +/** + * Defines the structure of the JSON object that is received from the pages.json file. + * + * @field title - The title of the page item + * @field type - The type of the page item. Home = Home Page (there can only be one home page), Group = collection of pages, Diagram = Diagram Page, Tree = Tree Page, and Table = Table Page + * @field path - The path to the configuration of the page item + * @field iconUrl - The url to the icon that gets displayed in the home page. This is typically the url to a SVG published to the web + * @field children - The children of the group items + * + */ +export default interface IPagesSchema { + title: string; + type: string; + path?: string; + iconUrl?: string; + children?: Children[]; +} + +/** + * Defines the structure of the child pages + * + * @field title - The title of the child page item + * @field type - The type of the child page item. Home = Home Page (there can only be one home page), Group = collection of pages, Diagram = Diagram Page, Tree = Tree Page, and Table = Table Page + * @field path - The path to the configuration of the child page item + * + */ +interface Children { + title: string; + type: string; + path: string; +} diff --git a/controller/src/schemas/commandSchema.ts b/controller/src/schemas/commandSchema.ts deleted file mode 100644 index 7d0cd32..0000000 --- a/controller/src/schemas/commandSchema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import ICommandSchema from '../interfaces/ICommandSchema'; -import { JSONSchemaType } from "ajv"; - -/** - * This JSON schema is used with AJV to validate JSON files stored in the commands directory that is stored within OML models. - * - * @remarks - * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. - * - * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. - * - */ - -export const commandSchema: JSONSchemaType = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "command": { - "type": "object", - "properties": { - "type": { - "type": "string" - } - }, - "required": ["type"] - } - }, - "required": ["name", "id", "command"] - } - } \ No newline at end of file diff --git a/controller/src/schemas/commands/commandSchema.ts b/controller/src/schemas/commands/commandSchema.ts new file mode 100644 index 0000000..9a06f8a --- /dev/null +++ b/controller/src/schemas/commands/commandSchema.ts @@ -0,0 +1,38 @@ +import ICommandSchema from "../../interfaces/ICommandSchema"; +import { JSONSchemaType } from "ajv"; + +/** + * This JSON schema is used with AJV to validate JSON files stored in the commands directory that is stored within OML models. + * + * @remarks + * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. + * + */ + +export const commandSchema: JSONSchemaType = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "array", + items: { + type: "object", + properties: { + name: { + type: "string", + }, + id: { + type: "string", + }, + command: { + type: "object", + properties: { + type: { + type: "string", + }, + }, + required: ["type"], + }, + }, + required: ["name", "id", "command"], + }, +}; diff --git a/controller/src/schemas/sparqlConfigSchema.ts b/controller/src/schemas/config/sparqlConfigSchema.ts similarity index 94% rename from controller/src/schemas/sparqlConfigSchema.ts rename to controller/src/schemas/config/sparqlConfigSchema.ts index 8ba33b2..cc3d792 100644 --- a/controller/src/schemas/sparqlConfigSchema.ts +++ b/controller/src/schemas/config/sparqlConfigSchema.ts @@ -1,4 +1,4 @@ -import ISparqlConfigSchema from '../interfaces/ISparqlConfigSchema'; +import ISparqlConfigSchema from '../../interfaces/ISparqlConfigSchema'; import { JSONSchemaType } from "ajv"; /** diff --git a/controller/src/schemas/validator.ts b/controller/src/schemas/validator.ts index db9e81b..b2cf482 100644 --- a/controller/src/schemas/validator.ts +++ b/controller/src/schemas/validator.ts @@ -1,6 +1,7 @@ import Ajv, { JSONSchemaType } from "ajv"; import ISparqlConfigSchema from "../interfaces/ISparqlConfigSchema"; import ICommandSchema from "../interfaces/ICommandSchema"; +import IPagesSchema from "../interfaces/IPagesSchema"; /** * This schema validator uses AJV to validate JSON data based on a schema interface. @@ -15,7 +16,7 @@ import ICommandSchema from "../interfaces/ICommandSchema"; * */ export const validateSchema = ( - schema: JSONSchemaType, + schema: JSONSchemaType, data: Object ) => { const ajv = new Ajv(); diff --git a/controller/src/schemas/viewpoints/pagesSchema.ts b/controller/src/schemas/viewpoints/pagesSchema.ts new file mode 100644 index 0000000..e02f8e2 --- /dev/null +++ b/controller/src/schemas/viewpoints/pagesSchema.ts @@ -0,0 +1,60 @@ +import IPagesSchema from "../../interfaces/IPagesSchema"; +import { JSONSchemaType } from "ajv"; + +/** + * This JSON schema is used with AJV to validate pages.json data that is stored within OML models. + * + * @remarks + * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * This schema has a recursive schema which you can read more {@link https://ajv.js.org/guide/combining-schemas.html | here}. + * + * This schema has nullable properties which you can read more {@link https://ajv.js.org/guide/typescript.html#utility-types-for-schemas | here}. + * + * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. + * + */ + +export const pagesSchema: JSONSchemaType = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "array", + items: { + type: "object", + properties: { + title: { + type: "string", + }, + type: { + type: "string", + }, + path: { + type: "string", + nullable: true, + }, + iconUrl: { + type: "string", + nullable: true, + }, + children: { + type: "array", + nullable: true, + items: { + type: "object", + properties: { + title: { + type: "string", + }, + type: { + type: "string", + }, + path: { + type: "string", + }, + }, + required: ["title", "type", "path"], + }, + }, + }, + required: ["title", "type"], + }, +}; diff --git a/controller/src/utilities/loaders/loadCommandFiles.ts b/controller/src/utilities/loaders/loadCommandFiles.ts index f6205aa..5e7d42d 100644 --- a/controller/src/utilities/loaders/loadCommandFiles.ts +++ b/controller/src/utilities/loaders/loadCommandFiles.ts @@ -2,7 +2,7 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TablePanel } from "../../panels/TablePanel"; import { PropertyPanelProvider } from "../../panels/PropertyPanelProvider"; import { validateSchema } from "../../schemas/validator"; -import { commandSchema } from "../../schemas/commandSchema"; +import { commandSchema } from "../../schemas/commands/commandSchema"; // TODO: handle multiple workspaces (currently assumes model is in the 1st) /** @@ -41,7 +41,7 @@ export const loadCommandFiles = async ( const validate = validateSchema(commandSchema, content); if (files.length > 0 && validate) { commands.executeCommand("setContext", "vision:hasCommand", true); - window.showInformationMessage("Command files loaded successfully."); + window.showInformationMessage(`${file} loaded successfully.`); } else { window.showErrorMessage(`Invalid or missing ${file}.`); } diff --git a/controller/src/utilities/loaders/loadSparqlConfigFiles.ts b/controller/src/utilities/loaders/loadSparqlConfigFiles.ts index e05ee46..a397a6c 100644 --- a/controller/src/utilities/loaders/loadSparqlConfigFiles.ts +++ b/controller/src/utilities/loaders/loadSparqlConfigFiles.ts @@ -1,7 +1,7 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TreeDataProvider } from "../../sidebar/TreeDataProvider"; import { validateSchema } from "../../schemas/validator"; -import { sparqlConfigSchema } from "../../schemas/sparqlConfigSchema"; +import { sparqlConfigSchema } from "../../schemas/config/sparqlConfigSchema"; export let globalQueryEndpoint: string = ""; export let globalPingEndpoint: string = ""; @@ -49,7 +49,7 @@ export async function loadSparqlConfigFiles() { true ); window.showInformationMessage( - "Sparql Config files loaded successfully." + `${file} loaded successfully.` ); TreeDataProvider.getInstance().updateHasSparqlConfig(true); } else { diff --git a/controller/src/utilities/loaders/loadViewpointFiles.ts b/controller/src/utilities/loaders/loadViewpointFiles.ts index 4e8d65b..63b8f1e 100644 --- a/controller/src/utilities/loaders/loadViewpointFiles.ts +++ b/controller/src/utilities/loaders/loadViewpointFiles.ts @@ -2,6 +2,8 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TreeDataProvider } from "../../sidebar/TreeDataProvider"; import { TablePanel } from "../../panels/TablePanel"; import { PropertyPanelProvider } from "../../panels/PropertyPanelProvider"; +import { validateSchema } from "../../schemas/validator"; +import { pagesSchema } from "../../schemas/viewpoints/pagesSchema"; // TODO: handle multiple workspaces (currently assumes model is in the 1st) /** @@ -20,6 +22,7 @@ export const loadViewpointFiles = async (viewpointContents: { TreeDataProvider.getInstance().updateHasPageViewpoint(false); const workspaceFolders = workspace.workspaceFolders; + if (workspaceFolders) { const uri = workspaceFolders[0].uri; loadPageFile(uri, viewpointContents); @@ -57,15 +60,25 @@ const loadPageFile = async ( const fileUri = Uri.joinPath(pageFolderUri, file); const buffer = await workspace.fs.readFile(fileUri); const content = JSON.parse(buffer.toString()); - try { - TreeDataProvider.getInstance().updateViewpoints(content); - TreeDataProvider.getInstance().updateHasPageViewpoint(true); - viewpointContents[file] = content; - } catch (parseErr) { - viewpointContents = {}; - throw new Error( - `Error parsing table viewpoint file ${file}: ${parseErr}` - ); + // Validate if the content matches the JSON Command Schema + const validate = validateSchema(pagesSchema, content); + if (files.length > 0 && validate) { + commands.executeCommand("setContext", "vision:hasCommand", true); + console.log(`${file} loaded successfully.`); + window.showInformationMessage(`${file} loaded successfully.`); + try { + TreeDataProvider.getInstance().updateViewpoints(content); + TreeDataProvider.getInstance().updateHasPageViewpoint(true); + viewpointContents[file] = content; + } catch (parseErr) { + viewpointContents = {}; + throw new Error( + `Error parsing table viewpoint file ${file}: ${parseErr}` + ); + } + } else { + console.error(`Invalid or missing ${file}.`); + window.showErrorMessage(`Invalid or missing ${file}.`); } } } @@ -73,9 +86,12 @@ const loadPageFile = async ( if ( err instanceof Error && err.message.startsWith("Error parsing viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading viewpoint files: ${err}`); window.showErrorMessage(`Error reading viewpoint files: ${err}`); } } @@ -115,19 +131,24 @@ const loadTableFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Table viewpoint files loaded successfully."); window.showInformationMessage( "Table viewpoint files loaded successfully." ); } else { + console.warn("Table viewpoint files not found."); window.showWarningMessage("Table viewpoint files not found."); } } catch (err) { if ( err instanceof Error && err.message.startsWith("Error parsing table viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading table viewpoint files: ${err}`); window.showErrorMessage(`Error reading table viewpoint files: ${err}`); } } finally { @@ -171,19 +192,24 @@ const loadTreeFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Tree viewpoint files loaded successfully."); window.showInformationMessage( "Tree viewpoint files loaded successfully." ); } else { + console.warn("Tree viewpoint files not found."); window.showWarningMessage("Tree viewpoint files not found."); } } catch (err) { if ( err instanceof Error && err.message.startsWith("Error parsing tree viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading tree viewpoint files: ${err}`); window.showErrorMessage(`Error reading tree viewpoint files: ${err}`); } } finally { @@ -227,13 +253,16 @@ const loadDiagramFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Diagram viewpoint files loaded successfully."); window.showInformationMessage( "Diagram viewpoint files loaded successfully." ); } else { + console.warn("Diagram viewpoint files not found."); window.showWarningMessage("Diagram viewpoint files not found."); } } catch (err) { + console.error(`Error reading diagram viewpoint files: ${err}`); window.showErrorMessage(`Error reading diagram viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel @@ -276,13 +305,16 @@ const loadPropertyFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Property viewpoint files loaded successfully."); window.showInformationMessage( "Property viewpoint files loaded successfully." ); } else { + console.warn("Property viewpoint files not found."); window.showWarningMessage("Property viewpoint files not found."); } } catch (err) { + console.error(`Error reading property viewpoint files: ${err}`); window.showErrorMessage(`Error reading property viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel @@ -325,13 +357,16 @@ const loadWizardFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Wizard viewpoint files loaded successfully."); window.showInformationMessage( "Wizard viewpoint files loaded successfully." ); } else { + console.warn("Wizard viewpoint files not found."); window.showWarningMessage("Wizard viewpoint files not found."); } } catch (err) { + console.error(`Error reading wizard viewpoint files: ${err}`); window.showErrorMessage(`Error reading wizard viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel