Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable package node migration for Dynamo Core 2.0+ graphs #9306

Merged
merged 17 commits into from
Dec 17, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion src/DynamoCore/Graph/Workspaces/SerializationConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ public class NodeReadConverter : JsonConverter
{
private CustomNodeManager manager;
private LibraryServices libraryServices;
private NodeFactory nodeFactory;
private bool isTestMode;


public ElementResolver ElementResolver { get; set; }
//map of all loaded assemblies including LoadFrom context assemblies
private Dictionary<string, List<Assembly>> loadedAssemblies;

[Obsolete("This constructor will be removed in Dynamo 3.0, please use new NodeReadConverter constructor with additional parameters to support node migration.")]
public NodeReadConverter(CustomNodeManager manager, LibraryServices libraryServices, bool isTestMode = false)
{
this.manager = manager;
Expand All @@ -56,6 +57,20 @@ public NodeReadConverter(CustomNodeManager manager, LibraryServices libraryServi
}
}

public NodeReadConverter(CustomNodeManager manager, LibraryServices libraryServices, NodeFactory nodeFactory, bool isTestMode = false)
{
this.manager = manager;
this.libraryServices = libraryServices;
this.nodeFactory = nodeFactory;
this.isTestMode = isTestMode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor but I think we avoid using this as per existing coding conventions (which may have been forgotten along the way). Also if you use Resharper you'll see that a lot of these things are highlighted by the tool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the old code and this file in particular is filled with this. Removing it in certain areas creates ambiguity between constructor parameters and class properties, such as above

//we only do this in test mode because it should not be required-
//see comment below in NodeReadConverter.ReadJson - and it could be slow.
if (this.isTestMode)
{
this.loadedAssemblies = this.buildMapOfLoadedAssemblies();
}
}

private Dictionary<string,List<Assembly>> buildMapOfLoadedAssemblies()
{
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
Expand Down Expand Up @@ -106,6 +121,18 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
}
}

// Check for and attempt to resolve an unknown type before proceeding
if (type == null)
{
// Check to see if the missing type can be resolved (AlsoKnownAs)
var unresolvedName = obj["$type"].Value<string>().Split(',').FirstOrDefault();
Type newType;
nodeFactory.ResolveType(unresolvedName, out newType);

if (newType != null) // If resolved update the type
{ type = newType; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it is preferred to have a single curly brace on a line (don't know what happened to coding conventions once followed by the team):

// If resolved update the type
if (newType != null) 
{ 
type = newType; 
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wow, if we would like to get this topic up, please fix all the comments in this PR so they are in the same format 😉
//[space][Comment]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry comments without a space were copied from the constructor I made obsolete, I would never commit such a crime 😂

}

// If the id is not a guid, makes a guid based on the id of the node
var guid = GuidUtility.tryParseOrCreateGuid(obj["Id"].Value<string>());

Expand All @@ -118,10 +145,13 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
string assemblyLocation = objectType.Assembly.Location;

bool remapPorts = true;

// If type is still null at this point return a dummy node
if (type == null)
{
node = CreateDummyNode(obj, assemblyLocation, inPorts, outPorts);
}
// Attempt to create a valid node using the type
else if (type == typeof(Function))
{
var functionId = Guid.Parse(obj["FunctionSignature"].Value<string>());
Expand Down Expand Up @@ -169,7 +199,15 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
else if (typeof(DSFunctionBase).IsAssignableFrom(type))
{
var mangledName = obj["FunctionSignature"].Value<string>();
var priorNames = libraryServices.GetPriorNames();
var functionDescriptor = libraryServices.GetFunctionDescriptor(mangledName);
string newName;

// Update the function descriptor if a newer migrated version of the node exists
if (priorNames.TryGetValue(mangledName, out newName))
{ functionDescriptor = libraryServices.GetFunctionDescriptor(newName); }

// Use the functionDescriptor to try and restore the proper node if possible
if (functionDescriptor == null)
{
node = CreateDummyNode(obj, assemblyLocation, inPorts, outPorts);
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1653,7 +1653,7 @@ public static WorkspaceModel FromJson(string json, LibraryServices libraryServic
Converters = new List<JsonConverter>{
new ConnectorConverter(logger),
new WorkspaceReadConverter(engineController, scheduler, factory, isTestMode, verboseLogging),
new NodeReadConverter(manager, libraryServices,isTestMode),
new NodeReadConverter(manager, libraryServices, factory, isTestMode),
new TypedParameterConverter()
},
ReferenceResolverProvider = () => { return new IdReferenceResolver(); }
Expand Down
4 changes: 4 additions & 0 deletions test/DynamoCoreTests/DynamoCoreTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
<Name>DynamoCore</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\..\src\DynamoPackages\DynamoPackages.csproj">
<Project>{47533b7c-0e1a-44a4-8511-b438645f052a}</Project>
<Name>DynamoPackages</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Engine\ProtoAssociative\ProtoAssociative.csproj">
<Project>{7318d5e5-9d15-4abe-8a51-92f58d4f0b85}</Project>
<Name>ProtoAssociative</Name>
Expand Down
34 changes: 33 additions & 1 deletion test/DynamoCoreTests/NodeMigrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using SystemTestServices;
using System.Xml;
using CoreNodeModels;
using CoreNodeModels.Input;
using CoreNodeModels.Logic;
using Dynamo.Graph.Nodes;
using Dynamo.Graph.Nodes.ZeroTouch;
using Dynamo.PackageManager;
using NUnit.Framework;
using PythonNodeModels;

namespace Dynamo.Tests
Expand Down Expand Up @@ -2242,6 +2244,36 @@ public void TestSaveDontCorruptForDeprecatedNodes()
}
#endregion

#region Dynamo Package Node Migration Tests
[Test]
[Category("UnitTests")]
public void TestPackageNodeMigrationForJSONGraphs()
{
// Define package loading reference paths
var dir = SystemTestBase.GetTestDirectory(ExecutingDirectory);
var pkgDir = Path.Combine(dir, "pkgs\\Dynamo Samples");
var legacyGraph = Path.Combine(pkgDir, "extra\\LegacyPackageSampleGraph.dyn");
var pkgMan = this.CurrentDynamoModel.GetPackageManagerExtension();
var loader = pkgMan.PackageLoader;
var pkg = loader.ScanPackageDirectory(pkgDir);

// Load the sample package which includes migrations
loader.Load(pkg);

// Assert expected package was loaded
Assert.AreEqual(pkg.Name, "Dynamo Samples");
Assert.AreEqual(3, loader.LocalPackages.Count());

// Load the legacy graph, which contains 4 ZeroTouch/NodeModel test cases that use
// old class names than were renamed in the version of the package we are loading.
// Verify these nodes don't appear as dummy node and are successfully migrated.
TestMigration(legacyGraph);

// Verify all 4 nodes exist in the workspace and were properly loaded/opened from above
Assert.AreEqual(4, this.CurrentDynamoModel.CurrentWorkspace.Nodes.Count());
}
#endregion

#region Private Helper Methods

private string GetDynPath(string sourceDynFile)
Expand Down
Binary file not shown.
15 changes: 15 additions & 0 deletions test/pkgs/Dynamo Samples/bin/SampleLibraryZeroTouch.Migrations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<migrations>
<priorNameHint>
<oldName>Examples.BasicExample.Create@Autodesk.DesignScript.Geometry.Point</oldName>
<newName>Examples.NEWBasicExample.Create@Autodesk.DesignScript.Geometry.Point</newName>
</priorNameHint>
<priorNameHint>
<oldName>Examples.BasicExample.Create@double,double,double</oldName>
<newName>Examples.NEWBasicExample.Create@double,double,double</newName>
</priorNameHint>
<priorNameHint>
<oldName>Examples.BasicExample.MultiReturnExample</oldName>
<newName>Examples.NEWBasicExample.MultiReturnExample</newName>
</priorNameHint>
</migrations>
Binary file not shown.
211 changes: 211 additions & 0 deletions test/pkgs/Dynamo Samples/extra/LegacyPackageSampleGraph.dyn
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
{
"Uuid": "3c91f94f-9c42-4a29-bf1c-ecdf340a3490",
"IsCustomNode": false,
"Description": null,
"Name": "LegacyDynamoSamples_MigrationTest",
"ElementResolver": {
"ResolutionMap": {}
},
"Inputs": [],
"Outputs": [],
"Nodes": [
{
"ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
"NodeType": "FunctionNode",
"FunctionSignature": "Examples.BasicExample.Create@Autodesk.DesignScript.Geometry.Point",
"Id": "da2c975424ea4e1bae46b5272758e3c5",
"Inputs": [
{
"Id": "a48fcf2706044ea3903eaebfa0daa8c9",
"Name": "point",
"Description": "A point.\n\nPoint\nDefault value : Autodesk.DesignScript.Geometry.Point.ByCoordinates(5, 5, 5)",
"UsingDefaultValue": true,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "7dc917d968794e26a94d3e2748163633",
"Name": "BasicExample",
"Description": "A BasicExample object.",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Auto",
"Description": "Another example of a static constructor which uses a parameter with a default value. The default value is provided as a design script expression.\n\nBasicExample.Create (point: Point = Autodesk.DesignScript.Geometry.Point.ByCoordinates(5, 5, 5)): BasicExample"
},
{
"ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
"NodeType": "FunctionNode",
"FunctionSignature": "Examples.BasicExample.Create@double,double,double",
"Id": "c61389b2f09c432ab8727cfd41282f9a",
"Inputs": [
{
"Id": "6b8a7b3b7b9d4bc28369802688120364",
"Name": "x",
"Description": "The x coordinate of the point.\n\ndouble\nDefault value : 42",
"UsingDefaultValue": true,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
},
{
"Id": "194e917177a24a27bccd80d4d61ad82e",
"Name": "y",
"Description": "The y coordinate of the point.\n\ndouble\nDefault value : 42",
"UsingDefaultValue": true,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
},
{
"Id": "3271aff8d6dd4c80be31a0ba305919fc",
"Name": "z",
"Description": "The z coordinate of the point.\n\ndouble\nDefault value : 42",
"UsingDefaultValue": true,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "d1fb031df35d49b688f7d8ef6d160dde",
"Name": "BasicExample",
"Description": "A HelloDynamoZeroTouch object.",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Auto",
"Description": "Dynamo uses the pattern of static constructors. Don't forget to fill in the xml comments so that you will get help tips in the UI. You can also use default parameters, as we have here. With default parameters defined, you will not be required to attach any inputs to these ports in Dynamo.\n\nBasicExample.Create (x: double = 42, y: double = 42, z: double = 42): BasicExample"
},
{
"ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
"NodeType": "FunctionNode",
"FunctionSignature": "Examples.BasicExample.MultiReturnExample",
"Id": "b932a2ef364945f39b7b1d06da2a0e97",
"Inputs": [],
"Outputs": [
{
"Id": "54a9e99405ad46c7acdd3a0f66115907",
"Name": "thing 1",
"Description": "var",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
},
{
"Id": "f0efc366311e458c8dbf8808b0cf0549",
"Name": "thing 2",
"Description": "var",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Auto",
"Description": "The MultiReturn attribute can be used to specify the names of multiple output ports on a node that returns a dictionary. The node must return a dictionary to be recognized as a multi-out node.\n\nBasicExample.MultiReturnExample ( ): var[]..[]"
},
{
"ConcreteType": "SampleLibraryUI.Examples.DropDownExample, SampleLibraryUI",
"SelectedIndex": 2,
"NodeType": "ExtensionNode",
"Id": "45152d03fc75491baa4a0f2f9565cd61",
"Inputs": [],
"Outputs": [
{
"Id": "da224d826dcc4469a3941bc070448b1c",
"Name": "item",
"Description": "The selected item",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Disabled",
"Description": "An example drop down node."
}
],
"Connectors": [],
"Dependencies": [],
"Bindings": [],
"View": {
"Dynamo": {
"ScaleFactor": 1.0,
"HasRunWithoutCrash": true,
"IsVisibleInDynamoLibrary": true,
"Version": "2.1.0.7116",
"RunType": "Automatic",
"RunPeriod": "1000"
},
"Camera": {
"Name": "Background Preview",
"EyeX": -17.0,
"EyeY": 24.0,
"EyeZ": 50.0,
"LookX": 12.0,
"LookY": -13.0,
"LookZ": -58.0,
"UpX": 0.0,
"UpY": 1.0,
"UpZ": 0.0
},
"NodeViews": [
{
"ShowGeometry": true,
"Name": "BasicExample.Create",
"Id": "da2c975424ea4e1bae46b5272758e3c5",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 264.0,
"Y": 34.75
},
{
"ShowGeometry": true,
"Name": "BasicExample.Create",
"Id": "c61389b2f09c432ab8727cfd41282f9a",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 275.0,
"Y": 130.75
},
{
"ShowGeometry": true,
"Name": "BasicExample.MultiReturnExample",
"Id": "b932a2ef364945f39b7b1d06da2a0e97",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 265.0,
"Y": 282.75
},
{
"ShowGeometry": true,
"Name": "Drop Down Example",
"Id": "45152d03fc75491baa4a0f2f9565cd61",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 318.0,
"Y": 408.75
}
],
"Annotations": [],
"X": 0.0,
"Y": 0.0,
"Zoom": 1.0
}
}
Loading