diff --git a/src/Eagle.ts b/src/Eagle.ts index f36860796..70035eb9c 100644 --- a/src/Eagle.ts +++ b/src/Eagle.ts @@ -2855,7 +2855,7 @@ export class Eagle { // validate edge const isValid: Errors.Validity = Edge.isValid(this,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, true, null); - if (isValid === Errors.Validity.Impossible || isValid === Errors.Validity.Invalid || isValid === Errors.Validity.Unknown){ + if (isValid === Errors.Validity.Impossible || isValid === Errors.Validity.Error || isValid === Errors.Validity.Unknown){ Utils.showUserMessage("Error", "Invalid edge"); return; } @@ -2900,7 +2900,7 @@ export class Eagle { // validate edge const isValid: Errors.Validity = Edge.isValid(this,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, true, null); - if (isValid === Errors.Validity.Impossible || isValid === Errors.Validity.Invalid || isValid === Errors.Validity.Unknown){ + if (isValid === Errors.Validity.Impossible || isValid === Errors.Validity.Error || isValid === Errors.Validity.Unknown){ Utils.showUserMessage("Error", "Invalid edge"); return; } @@ -4566,10 +4566,10 @@ export class Eagle { } checkGraph = (): void => { - const checkResult = Utils.checkGraph(this); - - this.graphWarnings(checkResult.warnings); - this.graphErrors(checkResult.errors); + Utils.checkGraph(this);//validate the graph + const graphErrors = Utils.gatherGraphErrors() //gather all the errors from all of the components + this.graphWarnings(graphErrors.warnings); + this.graphErrors(graphErrors.errors); }; showGraphErrors = (): void => { diff --git a/src/Edge.ts b/src/Edge.ts index 4c6c6676d..8b4322a33 100644 --- a/src/Edge.ts +++ b/src/Edge.ts @@ -329,18 +329,18 @@ export class Edge { // check that we are not connecting a Data component to a Data component, that is not supported if (sourceNode.getCategoryType() === Category.Type.Data && destinationNode.getCategoryType() === Category.Type.Data){ - Edge.isValidLog(edgeId, Errors.Validity.Invalid, Errors.Show("Data nodes may not be connected directly to other Data nodes", function(){Utils.showEdge(eagle, edgeId);}), showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, Errors.Show("Data nodes may not be connected directly to other Data nodes", function(){Utils.showEdge(eagle, edgeId);}), showNotification, showConsole, errorsWarnings); } // if source node or destination node is a construct, then something is wrong, constructs should not have ports if (sourceNode.getCategoryType() === Category.Type.Construct){ const issue: Errors.Issue = Errors.ShowFix("Edge (" + edgeId + ") cannot have a source node (" + sourceNode.getName() + ") that is a construct", function(){Utils.showEdge(eagle, edgeId)}, function(){Utils.fixMoveEdgeToEmbeddedApplication(eagle, edgeId)}, "Move edge to embedded application"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, issue, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, issue, showNotification, showConsole, errorsWarnings); } if (destinationNode.getCategoryType() === Category.Type.Construct){ const issue: Errors.Issue = Errors.ShowFix("Edge (" + edgeId + ") cannot have a destination node (" + destinationNode.getName() + ") that is a construct", function(){Utils.showEdge(eagle, edgeId)}, function(){Utils.fixMoveEdgeToEmbeddedApplication(eagle, edgeId)}, "Move edge to embedded application"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, issue, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, issue, showNotification, showConsole, errorsWarnings); } // if source node is a memory, and destination is a BashShellApp, OR @@ -349,7 +349,7 @@ export class Edge { if ((sourceNode.getCategory() === Category.Memory && destinationNode.getCategory() === Category.BashShellApp) || (sourceNode.getCategory() === Category.Memory && destinationNode.isGroup() && destinationNode.getInputApplication() !== undefined && destinationNode.hasInputApplication() && destinationNode.getInputApplication().getCategory() === Category.BashShellApp)){ const issue: Errors.Issue = Errors.ShowFix("output from Memory Node cannot be input into a BashShellApp or input into a Group Node with a BashShellApp inputApplicationType", function(){Utils.showNode(eagle, sourceNode.getId())}, function(){Utils.fixNodeCategory(eagle, sourceNode, Category.File, Category.Type.Data)}, "Change data component type to File"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, issue, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, issue, showNotification, showConsole, errorsWarnings); } const sourcePort : Field = sourceNode.findFieldById(sourcePortId); @@ -392,7 +392,7 @@ export class Edge { if (sourcePort !== null && destinationPort !== null){ // check that source and destination port are both event, or both not event if ((sourcePort.getIsEvent() && !destinationPort.getIsEvent()) || (!sourcePort.getIsEvent() && destinationPort.getIsEvent())){ - Edge.isValidLog(edgeId, Errors.Validity.Invalid, Errors.Show("Source port and destination port are mix of event and non-event ports", function(){Utils.showEdge(eagle, edgeId);}), showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, Errors.Show("Source port and destination port are mix of event and non-event ports", function(){Utils.showEdge(eagle, edgeId);}), showNotification, showConsole, errorsWarnings); } } @@ -422,7 +422,7 @@ export class Edge { // abort if source port and destination port have different data types if (!Utils.portsMatch(sourcePort, destinationPort)){ const x = Errors.ShowFix("Source and destination ports don't match data types: sourcePort (" + sourcePort.getDisplayText() + ":" + sourcePort.getType() + ") destinationPort (" + destinationPort.getDisplayText() + ":" + destinationPort.getType() + ")", function(){Utils.showEdge(eagle, edgeId);}, function(){Utils.fixPortType(eagle, sourcePort, destinationPort);}, "Overwrite destination port type with source port type"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } } @@ -450,7 +450,7 @@ export class Edge { if ( isSrcMatch && isDestMatch && edge.getId() !== edgeId){ const x = Errors.ShowFix("Edge is a duplicate. Another edge with the same source port and destination port already exists", function(){Utils.showEdge(eagle, edgeId);}, function(){Utils.fixDeleteEdge(eagle, edgeId);}, "Delete edge"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } } @@ -462,22 +462,22 @@ export class Edge { if (closesLoop){ if (!sourceNode.isData()){ const x = Errors.Show("Closes Loop Edge (" + edgeId + ") does not start from a Data component.", function(){Utils.showEdge(eagle, edgeId);}); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } if (!destinationNode.isApplication()){ const x = Errors.Show("Closes Loop Edge (" + edgeId + ") does not end at an Application component.", function(){Utils.showEdge(eagle, edgeId);}); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } if (!sourceNode.hasFieldWithDisplayText(Daliuge.FieldName.GROUP_END) || !Utils.asBool(sourceNode.getFieldByDisplayText(Daliuge.FieldName.GROUP_END).getValue())){ const x = Errors.ShowFix("'Closes Loop' Edge (" + edgeId + ") start node (" + sourceNode.getName() + ") does not have 'group_end' set to true.", function(){Utils.showEdge(eagle, edgeId);}, function(){Utils.fixFieldValue(eagle, sourceNode, Daliuge.groupEndField, "true")}, "Set 'group_end' to true"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } if (!destinationNode.hasFieldWithDisplayText(Daliuge.FieldName.GROUP_START) || !Utils.asBool(destinationNode.getFieldByDisplayText(Daliuge.FieldName.GROUP_START).getValue())){ const x = Errors.ShowFix("'Closes Loop' Edge (" + edgeId + ") end node (" + destinationNode.getName() + ") does not have 'group_start' set to true.", function(){Utils.showEdge(eagle, edgeId);}, function(){Utils.fixFieldValue(eagle, destinationNode, Daliuge.groupStartField, "true")}, "Set 'group_start' to true"); - Edge.isValidLog(edgeId, Errors.Validity.Invalid, x, showNotification, showConsole, errorsWarnings); + Edge.isValidLog(edgeId, Errors.Validity.Error, x, showNotification, showConsole, errorsWarnings); } } @@ -504,7 +504,7 @@ export class Edge { title = "Edge Impossible"; type = "danger"; break; - case Errors.Validity.Invalid: + case Errors.Validity.Error: title = "Edge Invalid"; type = "danger"; break; diff --git a/src/Errors.ts b/src/Errors.ts index 292dbe95c..3833ce668 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -123,7 +123,7 @@ export namespace Errors export enum Validity { Unknown = "Unknown", // validity of the edge is unknown Impossible = "Impossible", // never useful or valid - Invalid = "Invalid", // invalid, but possibly useful for expert users? change to error + Error = "Error", // invalid, but possibly useful for expert users? change to error Warning = "Warning", // valid, but some issue that the user should be aware of Fixable = "Fixable", // there is an issue with the connection but for drawing edges eagle will fix this for you Valid = "Valid" // fine diff --git a/src/Field.ts b/src/Field.ts index 30a798f40..1ed74f0de 100644 --- a/src/Field.ts +++ b/src/Field.ts @@ -41,6 +41,7 @@ export class Field { private inputAngle : number; private outputAngle : number; + private errorsArray : {issue:Errors.Issue, validity:Errors.Validity}[] private errorsWarnings : ko.Observable; constructor(id: string, displayText: string, value: string, defaultValue: string, description: string, readonly: boolean, type: string, precious: boolean, options: string[], positional: boolean, parameterType: Daliuge.FieldType, usage: Daliuge.FieldUsage, keyAttribute: boolean){ @@ -73,6 +74,7 @@ export class Field { this.inputAngle = 0; this.outputAngle = 0; + this.errorsArray = []; this.errorsWarnings = ko.observable({warnings: [], errors: []}); } @@ -348,6 +350,10 @@ export class Field { return this.errorsWarnings(); } + getErrors = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { + return this.errorsArray; + } + addErrorsWarnings(issue:Errors.Issue, issueType:string) : void { if(issueType === 'error'){ this.errorsWarnings().errors.push(issue) @@ -803,7 +809,7 @@ export class Field { static isValid(node:Node, field:Field, selectedLocation:Eagle.FileType, fieldIndex:number){ const eagle = Eagle.getInstance() - const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; + // const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; //checks for input ports if(field.isInputPort()){ @@ -827,7 +833,9 @@ export class Field { issue = Errors.ShowFix("Node " + node.getKey() + " (" + parentNode.getName() + ") has output application (" + node.getName() + ") with input port (" + field.getDisplayText() + ") whose type is not specified", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldType(eagle, field)}, ""); } } - errorsWarnings.warnings.push(issue); + + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } @@ -855,7 +863,8 @@ export class Field { issue = Errors.ShowFix("Node " + node.getKey() + " (" + parentNode.getName() + ") has output application (" + node.getName() + ") with output port (" + field.getDisplayText() + ") whose type is not specified", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldType(eagle, field)}, ""); } } - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } @@ -864,25 +873,29 @@ export class Field { //check that the field has an id if (field.getId() === "" || field.getId() === null){ const issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has field (" + field.getDisplayText() + ") with no id", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldId(eagle, field)}, "Generate id for field"); - errorsWarnings.errors.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Error}) + // errorsWarnings.errors.push(issue); } // check that the field has a default value if (field.getDefaultValue() === "" && !field.isType(Daliuge.DataType.String) && !field.isType(Daliuge.DataType.Password) && !field.isType(Daliuge.DataType.Object) && !field.isType(Daliuge.DataType.Unknown)) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a component parameter (" + field.getDisplayText() + ") whose default value is not specified", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldDefaultValue(eagle, field)}, "Generate default value for parameter"); - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } //chack that the field has a known type if (!Utils.validateType(field.getType())) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a component parameter (" + field.getDisplayText() + ") whose type (" + field.getType() + ") is unknown", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldType(eagle, field)}, "Prepend existing type (" + field.getType() + ") with 'Object.'"); - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } // check that the fields "key" is the same as the key of the node it belongs to if (field.getNodeKey() !== node.getKey()) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a field (" + field.getDisplayText() + ") whose key (" + field.getNodeKey() + ") doesn't match the node (" + node.getKey() + ")", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldKey(eagle, node, field)}, "Set field node key correctly"); - errorsWarnings.errors.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Error}) + // errorsWarnings.errors.push(issue); } //check that the field has a unique display text on the node @@ -895,10 +908,12 @@ export class Field { if (field.getDisplayText() === field1.getDisplayText() && field.getParameterType() === field1.getParameterType()){ if (field.getId() === field1.getId()){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has multiple attributes with the same display text and id (" + field.getDisplayText() + ").", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixNodeMergeFieldsByIndex(eagle, node, fieldIndex, j)}, "Merge fields"); - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } else { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has multiple attributes with the same display text (" + field.getDisplayText() + ").", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixNodeMergeFields(eagle, node, field, field1)}, "Merge fields"); - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } } } @@ -929,13 +944,14 @@ export class Field { const message = "Node " + node.getKey() + " (" + node.getName() + ") with category " + node.getCategory() + " contains field (" + field.getDisplayText() + ") with unsuitable type (" + field.getParameterType() + ")."; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldParameterType(eagle, node, field, suitableType)}, "Switch to suitable type, or remove if no suitable type"); - errorsWarnings.warnings.push(issue); + field.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } } - field.errorsWarnings(errorsWarnings) + // field.errorsWarnings(errorsWarnings) - return errorsWarnings + // return errorsWarnings } public static sortFunc(a: Field, b: Field) : number { diff --git a/src/GraphRenderer.ts b/src/GraphRenderer.ts index 076e88376..21fe0c65c 100644 --- a/src/GraphRenderer.ts +++ b/src/GraphRenderer.ts @@ -1765,7 +1765,7 @@ export class GraphRenderer { const linkValid : Errors.Validity = Edge.isValid(eagle,false, null, realSourceNode.getKey(), realSourcePort.getId(), realDestinationNode.getKey(), realDestinationPort.getId(), false, false, true, true, {errors:[], warnings:[]}); // abort if edge is invalid - if ((Setting.findValue(Setting.ALLOW_INVALID_EDGES) && linkValid === Errors.Validity.Invalid) || linkValid === Errors.Validity.Valid || linkValid === Errors.Validity.Warning){ + if ((Setting.findValue(Setting.ALLOW_INVALID_EDGES) && linkValid === Errors.Validity.Error) || linkValid === Errors.Validity.Valid || linkValid === Errors.Validity.Warning){ if (linkValid === Errors.Validity.Warning){ GraphRenderer.addEdge(realSourceNode, realSourcePort, realDestinationNode, realDestinationPort, true, false); } else { @@ -2125,7 +2125,7 @@ export class GraphRenderer { case Errors.Validity.Unknown: return "black"; case Errors.Validity.Impossible: - case Errors.Validity.Invalid: + case Errors.Validity.Error: return GraphConfig.getColor("edgeInvalid"); case Errors.Validity.Warning: return GraphConfig.getColor("edgeWarning"); @@ -2250,7 +2250,7 @@ export class GraphRenderer { // check if link has a warning or is invalid const linkValid : Errors.Validity = Edge.isValid(eagle,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, false, {errors:[], warnings:[]}); - if (linkValid === Errors.Validity.Invalid || linkValid === Errors.Validity.Impossible){ + if (linkValid === Errors.Validity.Error || linkValid === Errors.Validity.Impossible){ normalColor = GraphConfig.getColor('edgeInvalid'); selectedColor = GraphConfig.getColor('edgeInvalidSelected'); } diff --git a/src/GraphUpdater.ts b/src/GraphUpdater.ts index bf1f49dcb..815278f09 100644 --- a/src/GraphUpdater.ts +++ b/src/GraphUpdater.ts @@ -200,7 +200,8 @@ export class GraphUpdater { row.lastModified = date.toLocaleDateString() + " " + date.toLocaleTimeString() // check the graph once loaded - const results: Errors.ErrorsWarnings = Utils.checkGraph(eagle); + Utils.checkGraph(eagle); + const results: Errors.ErrorsWarnings = Utils.gatherGraphErrors(); row.numCheckWarnings = results.warnings.length; row.numCheckErrors = results.errors.length; } diff --git a/src/LogicalGraph.ts b/src/LogicalGraph.ts index 543092286..b86d2cee3 100644 --- a/src/LogicalGraph.ts +++ b/src/LogicalGraph.ts @@ -42,12 +42,14 @@ export class LogicalGraph { fileInfo : ko.Observable; private nodes : ko.ObservableArray; private edges : ko.ObservableArray; + private errorsArray : {issue:Errors.Issue, validity:Errors.Validity}[] constructor(){ this.fileInfo = ko.observable(new FileInfo()); this.fileInfo().type = Eagle.FileType.Graph; this.nodes = ko.observableArray([]); this.edges = ko.observableArray([]); + this.errorsArray = [] } static toOJSJson(graph : LogicalGraph, forTranslation : boolean) : object { @@ -313,6 +315,10 @@ export class LogicalGraph { return result; } + getErrors = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { + return this.errorsArray; + } + /** * Opens a dialog for selecting a data component type. */ @@ -787,5 +793,57 @@ export class LogicalGraph { static isValid () : void { //here should be the higher level graph wide checks for graph validity + const eagle = Eagle.getInstance() + const graph = eagle.logicalGraph() + + // check that all node, edge, field ids are unique + // { + const ids : string[] = []; + + // loop over graph nodes + for (const node of graph.getNodes()){ + //check for unique ids + if (ids.includes(node.getId())){ + const issue: Errors.Issue = Errors.ShowFix( + "Node (" + node.getName() + ") does not have a unique id", + function(){Utils.showNode(eagle, node.getId())}, + function(){Utils.newId(node)}, + "Assign node a new id" + ); + graph.errorsArray.push({issue : issue, validity : Errors.Validity.Error}) + // errorsWarnings.errors.push(issue); + } + ids.push(node.getId()); + + for (const field of node.getFields()){ + if (ids.includes(field.getId())){ + const issue: Errors.Issue = Errors.ShowFix( + "Field (" + field.getDisplayText() + ") on node (" + node.getName() + ") does not have a unique id", + function(){Utils.showNode(eagle, node.getId())}, + function(){Utils.newFieldId(eagle, node, field)}, + "Assign field a new id" + ); + graph.errorsArray.push({issue : issue, validity : Errors.Validity.Error}) + // errorsWarnings.errors.push(issue); + } + ids.push(field.getId()); + } + } + + // loop over graph edges + for (const edge of graph.getEdges()){ + if (ids.includes(edge.getId())){ + const issue: Errors.Issue = Errors.ShowFix( + "Edge (" + edge.getId() + ") does not have a unique id", + function(){Utils.showEdge(eagle, edge.getId())}, + function(){Utils.newId(edge)}, + "Assign edge a new id" + ); + graph.errorsArray.push({issue : issue, validity : Errors.Validity.Error}) + // errorsWarnings.errors.push(issue); + } + ids.push(edge.getId()); + } + // } } } diff --git a/src/Node.ts b/src/Node.ts index 72ee452af..6e3256c19 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -63,6 +63,7 @@ export class Node { private paletteDownloadUrl : ko.Observable; private dataHash : ko.Observable; + private errorsArray : {issue:Errors.Issue, validity:Errors.Validity}[] private errorsWarnings : ko.Observable; public static readonly DEFAULT_COLOR : string = "ffffff"; @@ -113,6 +114,7 @@ export class Node { this.paletteDownloadUrl = ko.observable(""); this.dataHash = ko.observable(""); + this.errorsArray = []; this.errorsWarnings = ko.observable({warnings: [], errors: []}); //graph related things @@ -1136,6 +1138,10 @@ export class Node { return this.errorsWarnings(); } + getErrors = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { + return this.errorsArray; + } + getAllErrorsWarnings = (): Errors.ErrorsWarnings => { const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; errorsWarnings.errors.push(...this.errorsWarnings().errors) @@ -1942,19 +1948,21 @@ export class Node { return x } - static isValid(node: Node, selectedLocation: Eagle.FileType) : Errors.ErrorsWarnings { + static isValid(node: Node, selectedLocation: Eagle.FileType) : void { const eagle = Eagle.getInstance() - const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; + // const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; // check that node has modern (not legacy) category if (node.getCategory() === Category.Component){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has legacy category (" + node.getCategory() + ")", function(){Utils.showNode(eagle, node.getId());}, function(){Utils.fixNodeCategory(eagle, node, Category.PythonApp, Category.Type.Application)}, ""); - errorsWarnings.warnings.push(issue); + // errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) } + // looping through and checking all the fields on the node for (let i = 0 ; i < node.getFields().length ; i++){ const field:Field = node.getFields()[i] - const fieldErrorsWarnings = Field.isValid(node,field,selectedLocation,i) + Field.isValid(node,field,selectedLocation,i) } // check that all nodes have correct numbers of inputs and outputs @@ -1963,22 +1971,26 @@ export class Node { if (node.getInputPorts().length < cData.minInputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too few input ports. A " + node.getCategory() + " component would typically have at least " + cData.minInputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } if ((node.getInputPorts().length - node.getInputEventPorts().length) > cData.maxInputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") has too many input ports. Should have at most " + cData.maxInputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } if (node.getOutputPorts().length < cData.minOutputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too few output ports. A " + node.getCategory() + " component would typically have at least " + cData.minOutputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } if ((node.getOutputPorts().length - node.getOutputEventPorts().length) > cData.maxOutputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too many output ports. Should have at most " + cData.maxOutputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } // check that all nodes should have at least one connected edge, otherwise what purpose do they serve? @@ -1994,27 +2006,34 @@ export class Node { // only check this if the component has been selected in the graph. If it was selected from the palette, it doesn't make sense to complain that it is not connected. if (!isConnected && !(cData.maxInputs === 0 && cData.maxOutputs === 0) && selectedLocation === Eagle.FileType.Graph){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has no connected edges. It should be connected to the graph in some way", function(){Utils.showNode(eagle, node.getId())}, null, ""); - errorsWarnings.warnings.push(issue); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Warning}) + // errorsWarnings.warnings.push(issue); } // check embedded application categories are not 'None' if (node.hasInputApplication() && node.getInputApplication().getCategory() === Category.None){ - errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has input application with category 'None'.")); + const issue: Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has input application with category 'None'.") + // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has input application with category 'None'.")); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Error}); } if (node.hasOutputApplication() && node.getOutputApplication().getCategory() === Category.None){ - errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has output application with category 'None'.")); + const issue : Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has output application with category 'None'.") + // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has output application with category 'None'.")); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Error}); } // check that Service nodes have inputApplications with no output ports! if (node.getCategory() === Category.Service && node.hasInputApplication() && node.getInputApplication().getOutputPorts().length > 0){ - errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") is a Service node, but has an input application with at least one output.")); + const issue : Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") is a Service node, but has an input application with at least one output.") + // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") is a Service node, but has an input application with at least one output.")); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Error}); } // check that this category of node contains all the fields it requires for (const requirement of Daliuge.categoryFieldsRequired){ if (requirement.categories.includes(node.getCategory())){ for (const requiredField of requirement.fields){ - Node._checkForField(eagle, node, requiredField, errorsWarnings); + Node._checkForField(eagle, node, requiredField); } } } @@ -2023,17 +2042,17 @@ export class Node { for (const requirement of Daliuge.categoryTypeFieldsRequired){ if (requirement.categoryTypes.includes(node.getCategoryType())){ for (const requiredField of requirement.fields){ - Node._checkForField(eagle, node, requiredField, errorsWarnings); + Node._checkForField(eagle, node, requiredField); } } } - node.errorsWarnings(errorsWarnings) + // node.errorsWarnings(errorsWarnings) - return errorsWarnings + // return errorsWarnings } - private static _checkForField(eagle: Eagle, node: Node, field: Field, errorsWarnings: Errors.ErrorsWarnings) : void { + private static _checkForField(eagle: Eagle, node: Node, field: Field) : void { // check if the node already has this field const existingField = node.getFieldByDisplayText(field.getDisplayText()); @@ -2041,11 +2060,15 @@ export class Node { // if so, check the attributes of the field match if (existingField === null){ const message = "Node " + node.getKey() + " (" + node.getName() + ":" + node.category() + ":" + node.categoryType() + ") does not have the required '" + field.getDisplayText() + "' field"; - errorsWarnings.errors.push(Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId());}, function(){Utils.addMissingRequiredField(eagle, node, field);}, "Add missing " + field.getDisplayText() + " field.")); + const issue : Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId());}, function(){Utils.addMissingRequiredField(eagle, node, field);}, "Add missing " + field.getDisplayText() + " field.") + // errorsWarnings.errors.push(Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId());}, function(){Utils.addMissingRequiredField(eagle, node, field);}, "Add missing " + field.getDisplayText() + " field.")); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Error}); } else { if (existingField.getParameterType() !== field.getParameterType()){ const message = "Node " + node.getKey() + " (" + node.getName() + ") has a '" + field.getDisplayText() + "' field with the wrong parameter type (" + existingField.getParameterType() + "), should be a " + field.getParameterType(); - existingField.addErrorsWarnings(Errors.ShowFix(message, function(){Utils.showField(eagle, node.getId(),existingField);}, function(){Utils.fixFieldParameterType(eagle, node, existingField, field.getParameterType())}, "Switch type of field to '" + field.getParameterType()),'error'); + const issue : Errors.Issue = Errors.ShowFix(message, function(){Utils.showField(eagle, node.getId(),existingField);}, function(){Utils.fixFieldParameterType(eagle, node, existingField, field.getParameterType())}, "Switch type of field to '" + field.getParameterType()) + // existingField.addErrorsWarnings(Errors.ShowFix(message, function(){Utils.showField(eagle, node.getId(),existingField);}, function(){Utils.fixFieldParameterType(eagle, node, existingField, field.getParameterType())}, "Switch type of field to '" + field.getParameterType()),'error'); + node.errorsArray.push({issue:issue,validity:Errors.Validity.Error}); } } } diff --git a/src/Setting.ts b/src/Setting.ts index 08899d3a8..df718e86c 100644 --- a/src/Setting.ts +++ b/src/Setting.ts @@ -397,7 +397,7 @@ const settings : SettingsGroup[] = [ new Setting(true, "Filter Node Suggestions", Setting.FILTER_NODE_SUGGESTIONS, "Filter Node Options When Drawing Edges Into Empty Space", false, Setting.Type.Boolean,true,true,true,true,false), new Setting(false, "STUDENT_SETTINGS_MODE", Setting.STUDENT_SETTINGS_MODE, "Mode disabling setting editing for students.", false, Setting.Type.Boolean, true, false,false, false, false), new Setting(true, "Value Editing", Setting.VALUE_EDITING_PERMS, "Set which values are allowed to be edited.", false, Setting.Type.Select, Setting.valueEditingPerms.KeyOnly,Setting.valueEditingPerms.Normal,Setting.valueEditingPerms.Normal,Setting.valueEditingPerms.ReadOnly,Setting.valueEditingPerms.ReadOnly, Object.values(Setting.valueEditingPerms)), - new Setting(true, "Auto-complete edges level", Setting.AUTO_COMPLETE_EDGES_LEVEL, "Specifies the minimum validity level of auto-complete edges displayed when dragging a new edge", false, Setting.Type.Select, Errors.Validity.Valid, Errors.Validity.Valid, Errors.Validity.Warning, Errors.Validity.Warning, Errors.Validity.Invalid, [Errors.Validity.Invalid, Errors.Validity.Warning, Errors.Validity.Valid]), + new Setting(true, "Auto-complete edges level", Setting.AUTO_COMPLETE_EDGES_LEVEL, "Specifies the minimum validity level of auto-complete edges displayed when dragging a new edge", false, Setting.Type.Select, Errors.Validity.Valid, Errors.Validity.Valid, Errors.Validity.Warning, Errors.Validity.Warning, Errors.Validity.Error, [Errors.Validity.Error, Errors.Validity.Warning, Errors.Validity.Valid]), ] ), new SettingsGroup( diff --git a/src/Utils.ts b/src/Utils.ts index 3e6840d71..1d056e24a 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1379,7 +1379,7 @@ export class Utils { static checkPalette(palette: Palette): Errors.ErrorsWarnings { const errorsWarnings: Errors.ErrorsWarnings = {warnings: [], errors: []}; - + const paletteErrors : {issue:Errors.Issue, validity:Errors.Validity}[]=[] // check for duplicate keys const keys: number[] = []; @@ -1394,55 +1394,63 @@ export class Utils { // check all nodes are valid for (const node of palette.getNodes()){ - const nodeErrorsWarnings = Node.isValid(node, Eagle.FileType.Palette); - errorsWarnings.errors.push(...nodeErrorsWarnings.errors) - errorsWarnings.warnings.push(...nodeErrorsWarnings.warnings) + Node.isValid(node, Eagle.FileType.Palette); + paletteErrors.push(...node.getErrors()) + // errorsWarnings.errors.push(...nodeErrorsWarnings.errors) + // errorsWarnings.warnings.push(...nodeErrorsWarnings.warnings) + } + + for(const error of paletteErrors){ + if(error.validity === Errors.Validity.Error){ + errorsWarnings.errors.push(error.issue) + }else{ + errorsWarnings.warnings.push(error.issue) + } } return errorsWarnings; } - static checkGraph(eagle: Eagle): Errors.ErrorsWarnings { - const errorsWarnings: Errors.ErrorsWarnings = {warnings: [], errors: []}; - + static checkGraph(eagle: Eagle): void { + // const errorsWarnings: Errors.ErrorsWarnings = {warnings: [], errors: []}; const graph: LogicalGraph = eagle.logicalGraph(); // check all nodes are valid for (const node of graph.getNodes()){ - const nodeErrorsWarnings = Node.isValid(node, Eagle.FileType.Graph); - errorsWarnings.errors.push(...nodeErrorsWarnings.errors) - errorsWarnings.warnings.push(...nodeErrorsWarnings.warnings) + Node.isValid(node, Eagle.FileType.Graph); + // errorsWarnings.errors.push(...nodeErrorsWarnings.errors) + // errorsWarnings.warnings.push(...nodeErrorsWarnings.warnings) //get the node's field errorswarnings - for(const field of node.getFields()){ - errorsWarnings.errors.push(...field.getErrorsWarnings().errors) - errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) - } + // for(const field of node.getFields()){ + // errorsWarnings.errors.push(...field.getErrorsWarnings().errors) + // errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) + // } // check the embedded applications - if (node.hasInputApplication()){ - const inputNodeErrorsWarnings = Node.isValid(node.getInputApplication(),Eagle.FileType.Graph) - errorsWarnings.errors.push(...inputNodeErrorsWarnings.errors) - errorsWarnings.warnings.push(...inputNodeErrorsWarnings.warnings) - - //get the input application's field errorswarnings - for(const field of node.getInputApplication().getFields()){ - errorsWarnings.errors.push(...field.getErrorsWarnings().errors) - errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) - } - } - if (node.hasOutputApplication()){ - const outputNodeErrorsWarnings = Node.isValid(node.getOutputApplication(),Eagle.FileType.Graph) - errorsWarnings.errors.push(...outputNodeErrorsWarnings.errors) - errorsWarnings.warnings.push(...outputNodeErrorsWarnings.warnings) + // if (node.hasInputApplication()){ + // const inputNodeErrorsWarnings = Node.isValid(node.getInputApplication(),Eagle.FileType.Graph) + // // errorsWarnings.errors.push(...inputNodeErrorsWarnings.errors) + // // errorsWarnings.warnings.push(...inputNodeErrorsWarnings.warnings) + + // //get the input application's field errorswarnings + // for(const field of node.getInputApplication().getFields()){ + // // errorsWarnings.errors.push(...field.getErrorsWarnings().errors) + // // errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) + // } + // } + // if (node.hasOutputApplication()){ + // const outputNodeErrorsWarnings = Node.isValid(node.getOutputApplication(),Eagle.FileType.Graph) + // // errorsWarnings.errors.push(...outputNodeErrorsWarnings.errors) + // // errorsWarnings.warnings.push(...outputNodeErrorsWarnings.warnings) - //get the output application's field errorswarnings - for(const field of node.getOutputApplication().getFields()){ - errorsWarnings.errors.push(...field.getErrorsWarnings().errors) - errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) - } - } + // //get the output application's field errorswarnings + // for(const field of node.getOutputApplication().getFields()){ + // // errorsWarnings.errors.push(...field.getErrorsWarnings().errors) + // // errorsWarnings.warnings.push(...field.getErrorsWarnings().warnings) + // } + // } } // check all edges are valid @@ -1450,50 +1458,104 @@ export class Utils { Edge.isValid(eagle,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, false, {warnings: [], errors: []}); } - // check that all node, edge, field ids are unique - { - const ids : string[] = []; - - // loop over graph nodes - for (const node of graph.getNodes()){ - //check for unique ids - if (ids.includes(node.getId())){ - const issue: Errors.Issue = Errors.ShowFix( - "Node (" + node.getName() + ") does not have a unique id", - function(){Utils.showNode(eagle, node.getId())}, - function(){Utils.newId(node)}, - "Assign node a new id" - ); - errorsWarnings.errors.push(issue); - } - ids.push(node.getId()); - - for (const field of node.getFields()){ - if (ids.includes(field.getId())){ - const issue: Errors.Issue = Errors.ShowFix( - "Field (" + field.getDisplayText() + ") on node (" + node.getName() + ") does not have a unique id", - function(){Utils.showNode(eagle, node.getId())}, - function(){Utils.newFieldId(eagle, node, field)}, - "Assign field a new id" - ); - errorsWarnings.errors.push(issue); - } - ids.push(field.getId()); + // // check that all node, edge, field ids are unique + // { + // const ids : string[] = []; + + // // loop over graph nodes + // for (const node of graph.getNodes()){ + // //check for unique ids + // if (ids.includes(node.getId())){ + // const issue: Errors.Issue = Errors.ShowFix( + // "Node (" + node.getName() + ") does not have a unique id", + // function(){Utils.showNode(eagle, node.getId())}, + // function(){Utils.newId(node)}, + // "Assign node a new id" + // ); + // // errorsWarnings.errors.push(issue); + // } + // ids.push(node.getId()); + + // for (const field of node.getFields()){ + // if (ids.includes(field.getId())){ + // const issue: Errors.Issue = Errors.ShowFix( + // "Field (" + field.getDisplayText() + ") on node (" + node.getName() + ") does not have a unique id", + // function(){Utils.showNode(eagle, node.getId())}, + // function(){Utils.newFieldId(eagle, node, field)}, + // "Assign field a new id" + // ); + // // errorsWarnings.errors.push(issue); + // } + // ids.push(field.getId()); + // } + // } + + // // loop over graph edges + // for (const edge of graph.getEdges()){ + // if (ids.includes(edge.getId())){ + // const issue: Errors.Issue = Errors.ShowFix( + // "Edge (" + edge.getId() + ") does not have a unique id", + // function(){Utils.showEdge(eagle, edge.getId())}, + // function(){Utils.newId(edge)}, + // "Assign edge a new id" + // ); + // // errorsWarnings.errors.push(issue); + // } + // ids.push(edge.getId()); + // } + // } + + // return errorsWarnings; + } + + static gatherGraphErrors(): Errors.ErrorsWarnings { + const eagle = Eagle.getInstance() + const errorsWarnings: Errors.ErrorsWarnings = {warnings: [], errors: []}; + const graphErrors : {issue:Errors.Issue, validity:Errors.Validity}[] = [] + const graph : LogicalGraph = eagle.logicalGraph() + + //gather all the errors + //from nodes + for(const node of graph.getNodes()){ + graphErrors.push(...node.getErrors()) + + //fields + for( const field of node.getFields()){ + graphErrors.push(...field.getErrors()) + } + + //embedded input applications and their fields + if(node.hasInputApplication()){ + graphErrors.push(...node.getInputApplication().getErrors()) + + for( const field of node.getInputApplication().getFields()){ + graphErrors.push(...field.getErrors()) } } - // loop over graph edges - for (const edge of graph.getEdges()){ - if (ids.includes(edge.getId())){ - const issue: Errors.Issue = Errors.ShowFix( - "Edge (" + edge.getId() + ") does not have a unique id", - function(){Utils.showEdge(eagle, edge.getId())}, - function(){Utils.newId(edge)}, - "Assign edge a new id" - ); - errorsWarnings.errors.push(issue); + //embedded output applications and their fields + if(node.hasOutputApplication()){ + graphErrors.push(...node.getOutputApplication().getErrors()) + + for( const field of node.getOutputApplication().getFields()){ + graphErrors.push(...field.getErrors()) } - ids.push(edge.getId()); + } + } + + // from edges + for (const edge of graph.getEdges()){ + graphErrors.push(...edge.getErrorsArray()) + } + + //from logical graph + graphErrors.push(...graph.getErrors()) + + for(const error of graphErrors){ + if(error.validity === Errors.Validity.Error || error.validity === Errors.Validity.Impossible || error.validity === Errors.Validity.Unknown){ + errorsWarnings.errors.push(error.issue) + }else{ + errorsWarnings.warnings.push(error.issue) } } @@ -2190,7 +2252,7 @@ export class Utils { } if (errorsWarnings.errors.length !== 0){ - return Errors.Validity.Invalid; + return Errors.Validity.Error; } return Errors.Validity.Warning;