1 Commits

Author SHA1 Message Date
dependabot[bot] 5c6d44394c Bump actions/setup-dotnet from 3.2.0 to 4.0.0
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3.2.0 to 4.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v3.2.0...v4.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-28 14:26:43 +00:00
6 changed files with 17 additions and 537 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.1
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v3.2.0
uses: actions/setup-dotnet@v4.0.0
with:
dotnet-version: 7.x
- name: Restore dependencies
+1 -1
View File
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpeckleAutomateDotnetExample", "SpeckleAutomateDotnetExample\SpeckleAutomateDotnetExample.csproj", "{E1DE5809-1111-4817-9B7D-7ADCA087FA1C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpeckleAutomateDotnetExample", "SpeckleAutomateDotnetExample/SpeckleAutomateDotnetExample.csproj", "{E1DE5809-1111-4817-9B7D-7ADCA087FA1C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+11 -254
View File
@@ -1,16 +1,11 @@
using Objects;
using Objects.Geometry;
using Speckle.Automate.Sdk;
using Speckle.Core.Api;
using Speckle.Core.Models;
using SpeckleAutomateDotnetExample;
using Speckle.Core.Logging;
using Speckle.Core.Models.Extensions;
static class AutomateFunction
{
public static string ADDED = "ADDED";
public static string MODIFIED = "MODIFIED";
public static string DELETED = "DELETED";
public static string UNCHANGED = "UNCHANGED";
public static async Task Run(
AutomationContext automationContext,
FunctionInputs functionInputs
@@ -19,254 +14,16 @@ static class AutomateFunction
Console.WriteLine("Starting execution");
_ = typeof(ObjectsKit).Assembly; // INFO: Force objects kit to initialize
double tolerance = functionInputs.Tolerance;
Console.WriteLine("Receiving version");
var commitObject = await automationContext.ReceiveVersion();
// get the testing and release branches
string testBranchName = automationContext.AutomationRunData.BranchName;
string releaseBranchName = testBranchName.Replace("/testing", "/release");
Console.WriteLine($"Comparing {testBranchName} against {releaseBranchName}");
Branch? releaseBranch = await automationContext.SpeckleClient
.BranchGet(automationContext.AutomationRunData.ProjectId, releaseBranchName, 1)
.ConfigureAwait(false);
if (releaseBranch is null)
{
throw new Exception("Release branch was null");
}
Console.WriteLine("Received version: " + commitObject);
// get the release branch latest commit
Commit releaseCommit = releaseBranch.commits.items.First();
if (releaseCommit is null)
{
throw new Exception("Release branch has no commits");
}
var count = commitObject
.Flatten()
.Count(b => b.speckle_type == functionInputs.SpeckleTypeToCount);
// get the test and release commit base
Console.WriteLine("Receiving test version");
Base testingCommitObject = await automationContext.ReceiveVersion();
Console.WriteLine("Received test version: " + testingCommitObject);
Console.WriteLine("Receiving release version");
Base releaseCommitObject = await Utils.RecieveVersionAsync(
releaseCommit.id,
automationContext
);
Console.WriteLine("Received release version: " + releaseCommitObject);
// Create dictionaries by appId (or speckle id if no appId exists) for the release and testing commits
// note that it is possible for multiple objects to have the same app id
// (eg mapped schema objects have the same id as their parent geometry)
// and it is also possible for multiple objects (with no app id) to have the same speckle id
// (eg the same mesh sent from gh multiple times)
Dictionary<string, List<Base>> releaseCommitAppIdDict = new();
Dictionary<string, List<Base>> releaseCommitSpeckleIdDict = new();
Dictionary<string, List<Base>> testingCommitAppIdDict = new();
Dictionary<string, List<Base>> testingCommitSpeckleIdDict = new();
Utils.CreateDictionaryFromBaseById(
releaseCommitObject,
out releaseCommitAppIdDict,
out releaseCommitSpeckleIdDict,
out int releaseObjectCount
);
Console.WriteLine(
$"Found {releaseObjectCount} objects in RELEASE with {releaseCommitAppIdDict.Count} unique applicationIds and {releaseCommitSpeckleIdDict} unique speckle ids (for objects with no application id)."
);
Utils.CreateDictionaryFromBaseById(
testingCommitObject,
out testingCommitAppIdDict,
out testingCommitSpeckleIdDict,
out int testingObjectCount
);
Console.WriteLine(
$"Found {testingObjectCount} objects in TESTING with {testingCommitAppIdDict.Count} unique applicationIds and {testingCommitSpeckleIdDict} unique speckle ids (for objects with no application id)."
);
// COMPARE COMMIT OBJECTS WITH APPLICATION IDS
// and store in hash lists where each object is (id, appId, type), and for modified, and additional string of property changes.
HashSet<Tuple<string, string?, string>> deletedAppIdObjects = new();
HashSet<Tuple<string, string?, string>> unchangedAppIdObjects = new();
HashSet<Tuple<string, string?, string>> addedAppIdObjects = new();
HashSet<Tuple<string, string?, string, string>> modifiedAppIdObjects = new();
// first find deleted objects in the testing commit and remove their keys from the release commit dict
foreach (string releaseAppId in releaseCommitAppIdDict.Keys)
{
if (!testingCommitAppIdDict.ContainsKey(releaseAppId))
{
releaseCommitAppIdDict[releaseAppId].ForEach(
o =>
deletedAppIdObjects.Add(
new Tuple<string, string?, string>(o.id, releaseAppId, o.speckle_type)
)
);
releaseCommitAppIdDict.Remove(releaseAppId);
}
}
// then find unchanged, added, and modified objects by iterating through testing commit app ids
foreach (string testingAppId in testingCommitAppIdDict.Keys)
{
List<Base> testObjects = testingCommitAppIdDict[testingAppId];
// test for added objects
if (!releaseCommitAppIdDict.ContainsKey(testingAppId))
{
testObjects.ForEach(
o =>
addedAppIdObjects.Add(
new Tuple<string, string?, string>(o.id, testingAppId, o.speckle_type)
)
);
}
else
{
List<Base> releaseObjects = releaseCommitAppIdDict[testingAppId];
// test for unchanged objects
// by filtering the testing and release objects with matching speckle ids
Utils
.FilterListsBySpeckleIdMatch(testObjects, releaseObjects)
.ForEach(o => unchangedAppIdObjects.Add(o));
// for remaining objects, determine deleted objects
// and then compare them in order (assume modified) and handle leftovers (added)
// this is imperfect, as there's a chance we are not comparing the correct objects.
if (releaseObjects.Count > testObjects.Count)
{
for (int i = releaseObjects.Count - 1; i >= testObjects.Count; i--)
{
deletedAppIdObjects.Add(
new Tuple<string, string?, string>(
releaseObjects[i].id,
testingAppId,
releaseObjects[i].speckle_type
)
);
releaseObjects.RemoveAt(i);
}
}
for (int i = 0; i < testObjects.Count; i++)
{
Base testObject = testObjects[i];
if (i < releaseObjectCount)
{
Base releaseObject = releaseObjects[i];
// compare object properties to determine changes
List<string> addedProps = new();
List<string> deletedProps = new();
List<Tuple<string, string>> modifiedProps = new();
Utils.CompareBaseProperties(
testObject,
releaseObject,
out addedProps,
out deletedProps,
out modifiedProps
);
var sb = new System.Text.StringBuilder();
addedProps.ForEach(s => sb.AppendLine($"{ADDED} prop ({s})."));
deletedProps.ForEach(s => sb.AppendLine($"{DELETED} prop ({s})."));
modifiedProps.ForEach(
t => sb.AppendLine($"{MODIFIED} ({t.Item2}) of prop ({t.Item1})")
);
modifiedAppIdObjects.Add(
new Tuple<string, string?, string, string>(
testObject.id,
testObject.applicationId,
testObject.speckle_type,
sb.ToString()
)
);
}
// remaining test objects are considered added
else
{
addedAppIdObjects.Add(
new Tuple<string, string?, string>(
testObject.id,
testingAppId,
testObject.speckle_type
)
);
}
}
}
}
// COMPARE COMMIT OBJECTS WITHOUT APPLICATION IDS USING SPECKLE IDS
// since we only have 1 parameter of comparison, we can rule out any matching speckle ids as unchanged.
// for all other objects, mark as modified, and indicate quantity of any added or deleted objects
// store modified in hash lists of (id, type)
HashSet<Tuple<string, string?, string>> unchangedSpeckleIdObjects = new();
HashSet<Tuple<string, string>> changedSpeckleIdObjects = new();
// first filter out matching speckle ids
List<Base> flattenedTestingSpeckleIdDict = testingCommitSpeckleIdDict.Values
.SelectMany(o => o)
.ToList();
List<Base> flattenedReleaseSpeckleIdDict = releaseCommitSpeckleIdDict.Values
.SelectMany(o => o)
.ToList();
Utils
.FilterListsBySpeckleIdMatch(
flattenedTestingSpeckleIdDict,
flattenedReleaseSpeckleIdDict
)
.ForEach(o => unchangedSpeckleIdObjects.Add(o));
// then store all remaining testing objects as changed
flattenedTestingSpeckleIdDict.ForEach(
o => changedSpeckleIdObjects.Add(new Tuple<string, string>(o.id, o.speckle_type))
);
// calculate count difference
int speckleIdObjectCountDifference =
flattenedTestingSpeckleIdDict.Count - flattenedReleaseSpeckleIdDict.Count;
// REPORT ALL DIFF RESULTS FOR APP IDS AND SPECKLE IDS
// mark run succeeded if there are no added, modified, or deleted app id objects, and no changed speckle id objects
// mark run failed otherwise
if (
addedAppIdObjects.Count
+ deletedAppIdObjects.Count
+ modifiedAppIdObjects.Count
+ changedSpeckleIdObjects.Count
== 0
)
{
automationContext.MarkRunSuccess($"Run passed with no changes to objects.");
}
else
{
automationContext.MarkRunFailed(
$"Run failed due to: {addedAppIdObjects.Count} {ADDED}, {modifiedAppIdObjects.Count} {MODIFIED}, and {deletedAppIdObjects.Count} {DELETED} objects WITH APP IDS, and {(speckleIdObjectCountDifference > 0 ? $"{speckleIdObjectCountDifference} {ADDED}" : $"{Math.Abs(speckleIdObjectCountDifference)} {DELETED}")} and {changedSpeckleIdObjects.Count} CHANGED objects WITHOUT APP IDS compared to the release commit. "
);
foreach (var added in addedAppIdObjects)
{
Console.WriteLine(
$"{ADDED} {added.Item3} object: id( {added.Item1} ), appId: {added.Item2}"
);
}
foreach (var deleted in deletedAppIdObjects)
{
Console.WriteLine(
$"{DELETED} {deleted.Item3} object: id( {deleted.Item1} ), appId: {deleted.Item2}"
);
}
foreach (var modified in modifiedAppIdObjects)
{
Console.WriteLine(
$"{MODIFIED} {modified.Item3} object: id( {modified.Item1} ), appId: {modified.Item2}. Changed props: {modified.Item4}"
);
}
foreach (var changed in changedSpeckleIdObjects)
{
Console.WriteLine($"CHANGED {changed.Item2} object: id( {changed.Item1} )");
}
}
Console.WriteLine($"Counted {count} objects");
automationContext.MarkRunSuccess($"Counted {count} objects");
}
}
@@ -7,8 +7,6 @@ using System.ComponentModel.DataAnnotations;
/// are valid and match the required schema.
struct FunctionInputs
{
//[Required]
public double Tolerance;
//public string Exclusions;
[Required]
public string SpeckleTypeToCount;
}
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.Automate.Sdk" Version="2.18.0-fileInput" />
<PackageReference Include="Speckle.Objects" Version="2.18.0-fileInput" />
<PackageReference Include="Speckle.Automate.Sdk" Version="2.17.0-automate3" />
<PackageReference Include="Speckle.Objects" Version="2.17.0-automate3" />
</ItemGroup>
</Project>
-275
View File
@@ -1,275 +0,0 @@
using Speckle.Automate.Sdk;
using Speckle.Automate.Sdk.Schema;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Models.Extensions;
using Speckle.Core.Transports;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpeckleAutomateDotnetExample
{
internal class Utils
{
/// <summary>
/// Receives a commit object from the same project and account as the <paramref name="context"/>
/// </summary>
/// <param name="commitId"> The id of the commit to receive</param>
/// <param name="context">The Automation context</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<Base> RecieveVersionAsync(
string commitId,
AutomationContext context
)
{
ServerTransport serverTransport = new ServerTransport(
context.SpeckleClient.Account,
context.AutomationRunData.ProjectId
);
Base? receivedCommitObject = await Operations
.Receive(
(
await context.SpeckleClient
.CommitGet(context.AutomationRunData.ProjectId, commitId)
.ConfigureAwait(continueOnCapturedContext: false)
).referencedObject,
serverTransport,
new MemoryTransport()
)
.ConfigureAwait(continueOnCapturedContext: false);
if (receivedCommitObject == null)
{
throw new Exception("Commit root object was null");
}
return receivedCommitObject;
}
/// <summary>
/// Creates Dictionaries from a commit Base, using applicationId if available or speckle id if no applicationId exists.
/// </summary>
/// <param name="commit">The commit Base to create dictionaries from</param>
/// <param name="appIdDict">The dictionary of commit objects sorted by applicationId</param>
/// <param name="speckleIdDict">The dictionary of commit objects without applicationIds, sorted by speckle id</param>
public static void CreateDictionaryFromBaseById(
Base commit,
out Dictionary<string, List<Base>> appIdDict,
out Dictionary<string, List<Base>> speckleIdDict,
out int objectCount
)
{
IEnumerable<Base> commitObjects = commit.Flatten();
objectCount = commitObjects.Count();
appIdDict = new Dictionary<string, List<Base>>();
speckleIdDict = new Dictionary<string, List<Base>>();
foreach (var commitObject in commitObjects)
{
if (!string.IsNullOrWhiteSpace(commitObject.applicationId))
{
if (appIdDict.ContainsKey(commitObject.applicationId))
{
appIdDict[commitObject.applicationId].Add(commitObject);
}
else
{
appIdDict.Add(
commitObject.applicationId,
new List<Base>() { commitObject }
);
}
}
else
{
if (speckleIdDict.ContainsKey(commitObject.id))
{
speckleIdDict[commitObject.id].Add(commitObject);
}
else
{
speckleIdDict.Add(commitObject.id, new List<Base>() { commitObject });
}
}
}
}
/// <summary>
/// Filters two lists by removing all objects from <paramref name="setA"/> with a matching speckle id in <paramref name="setB"/>
/// </summary>
/// <param name="setA">The list to filter</param>
/// <param name="setB">The list to filter against</param>
/// <returns>A list of matches found in <paramref name="setA"/></returns>
public static List<Tuple<string, string?, string>> FilterListsBySpeckleIdMatch(
List<Base> setA,
List<Base> setB
)
{
var matches = new List<Tuple<string, string?, string>>();
for (int i = setA.Count - 1; i >= 0; i--)
{
var testObject = setA[i];
for (int j = setB.Count - 1; j >= 0; j--)
{
var releaseObject = setB[j];
// if a match was found, remove from both lists and add to matches
if (testObject.id == releaseObject.id)
{
matches.Add(
new Tuple<string, string?, string>(
testObject.id,
testObject.applicationId,
testObject.speckle_type
)
);
setA.RemoveAt(i);
setB.RemoveAt(j);
}
}
}
return matches;
}
/// <summary>
/// Compares the properties of <paramref name="a"/> against <paramref name="b"/>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="addedProps">Properties on <paramref name="a"/> that do not exist on <paramref name="b"/></param>
/// <param name="deletedProps">Properties on <paramref name="b"/> that do not exist on <paramref name="a"/></param>
/// <param name="modifiedProps">Properties on <paramref name="a"/> that have a different value on <paramref name="b"/>. The second string is the category of modification (eg count/primitive/base)</param>
public static void CompareBaseProperties(
Base a,
Base b,
out List<string> addedProps,
out List<string> deletedProps,
out List<Tuple<string, string>> modifiedProps
)
{
addedProps = new List<string>();
deletedProps = new List<string>();
modifiedProps = new List<Tuple<string, string>>();
Dictionary<string, object?> bObjectPropDict = b.GetMembers();
Dictionary<string, object?> aObjectPropDict = a.GetMembers();
foreach (KeyValuePair<string, object?> entry in aObjectPropDict)
{
if (bObjectPropDict.ContainsKey(entry.Key))
{
bool changed = !IsEqual(
entry.Value,
bObjectPropDict[entry.Key],
out string category
);
if (changed)
{
modifiedProps.Add(new Tuple<string, string>(entry.Key, category));
}
bObjectPropDict.Remove(entry.Key);
}
else
{
addedProps.Add(entry.Key);
}
}
// check if there are any props left on the b - these were missing in the a
foreach (var entry in bObjectPropDict)
{
deletedProps.Add(entry.Key);
}
}
/// <summary>
/// Determines equality between two objects
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="category">The category of the modification, if objects are not equal</param>
/// <returns></returns>
public static bool IsEqual<T>(T a, T b, out string category)
{
category = "Value";
switch (a)
{
case Base aBase:
if (b is Base bBase && bBase.speckle_type == aBase.speckle_type)
{
return aBase.id == bBase.id;
}
else
{
category = "Type";
return false;
}
case List<object> aList:
if (b is List<object> bList)
{
if (aList.Count != bList.Count)
{
category = "Count";
return false;
}
for (int i = 0; i < aList.Count; i++)
{
if (!Equals(aList[i], bList[i]))
{
return false;
}
}
return true;
}
else
{
category = "Type";
return false;
}
case Dictionary<string, object> aDictionary:
if (b is Dictionary<string, object> bDictionary)
{
if (aDictionary.Count != bDictionary.Count)
{
category = "Count";
return false;
}
foreach (var entry in aDictionary)
{
if (
!bDictionary.ContainsKey(entry.Key)
|| !Equals(entry.Value, bDictionary[entry.Key])
)
{
return false;
}
}
return true;
}
else
{
category = "Type";
return false;
}
default:
try
{
return EqualityComparer<T>.Default.Equals(a, b);
}
catch
{
return false;
}
}
}
}
}