-
Notifications
You must be signed in to change notification settings - Fork 161
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
CNX-8397-DLL-Conflict-Handling in existing DUI2 connectors #3273
Merged
Merged
Changes from 7 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
35e75c3
add conflict management project
a792905
rename namespace
9a2a77b
don't over catch exception
f3d14de
save options to different location
7e444b9
fix: Minor tweaks in csproj for conflictmanager
AlanRynne 33d6c50
add metrics through events
1358fec
create undefined user
0b0c917
add dtos for sending conflict info to mixpanel
af77d11
Merge remote-tracking branch 'origin/dev' into CNX-8397-DLL-Conflict-…
7dfa78e
fix conflictInfoDto
d1fd72e
Merge remote-tracking branch 'origin/dev' into CNX-8397-DLL-Conflict-…
888ef8a
only track in release mode
2e762b9
move events to its own file and fix build
0d885c5
fix ci errors
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
209 changes: 209 additions & 0 deletions
209
ConnectorCore/DllConflictManagement/Analytics/AnalyticsWithoutDependencies.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
using System.Diagnostics; | ||
using System.Net.Http.Headers; | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using System.Web; | ||
using Speckle.DllConflictManagement.EventEmitter; | ||
using Speckle.DllConflictManagement.Serialization; | ||
|
||
namespace Speckle.DllConflictManagement.Analytics; | ||
|
||
/// <summary> | ||
/// A version of the Analytics class in Core that doesn't have any dependencies. This class will load and subscribe | ||
/// to the eventEmitter's Action event, but will hopefully get unsubscribed and replaced by the full version in Core | ||
/// </summary> | ||
public sealed class AnalyticsWithoutDependencies | ||
{ | ||
private const string MIXPANEL_TOKEN = "acd87c5a50b56df91a795e999812a3a4"; | ||
private const string MIXPANEL_SERVER = "https://analytics.speckle.systems"; | ||
private readonly ISerializer _serializer; | ||
private readonly string _hostApplication; | ||
private readonly string _hostApplicationVersion; | ||
private readonly DllConflictEventEmitter _eventEmitter; | ||
|
||
public AnalyticsWithoutDependencies( | ||
DllConflictEventEmitter eventEmitter, | ||
ISerializer serializer, | ||
string hostApplication, | ||
string hostApplicationVersion | ||
) | ||
{ | ||
_eventEmitter = eventEmitter; | ||
_serializer = serializer; | ||
_hostApplication = hostApplication; | ||
_hostApplicationVersion = hostApplicationVersion; | ||
} | ||
|
||
/// <summary> | ||
/// <see langword="false"/> when the DEBUG pre-processor directive is <see langword="true"/>, <see langword="false"/> otherwise | ||
/// </summary> | ||
/// <remarks>This must be kept as a computed property, not a compile time const</remarks> | ||
internal static bool IsReleaseMode => | ||
#if DEBUG | ||
false; | ||
#else | ||
true; | ||
#endif | ||
|
||
/// <summary> | ||
/// Tracks an event without specifying the email and server. | ||
/// It's not always possible to know which account the user has selected, especially in visual programming. | ||
/// Therefore we are caching the email and server values so that they can be used also when nodes such as "Serialize" are used. | ||
/// If no account info is cached, we use the default account data. | ||
/// </summary> | ||
/// <param name="eventName">Name of the even</param> | ||
/// <param name="customProperties">Additional parameters to pass in to event</param> | ||
/// <param name="isAction">True if it's an action performed by a logged user</param> | ||
public void TrackEvent(Events eventName, Dictionary<string, object>? customProperties = null, bool isAction = true) | ||
{ | ||
Task.Run(async () => await TrackEventAsync(eventName, customProperties, isAction).ConfigureAwait(false)); | ||
} | ||
|
||
/// <summary> | ||
/// Tracks an event from a specified email and server, anonymizes personal information | ||
/// </summary> | ||
/// <param name="eventName">Name of the event</param> | ||
/// <param name="customProperties">Additional parameters to pass to the event</param> | ||
/// <param name="isAction">True if it's an action performed by a logged user</param> | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage( | ||
"Design", | ||
"CA1031:Do not catch general exception types", | ||
Justification = "Catching all exceptions to avoid an unobserved exception that could crash the host app" | ||
)] | ||
private async Task TrackEventAsync( | ||
Events eventName, | ||
Dictionary<string, object>? customProperties = null, | ||
bool isAction = true | ||
) | ||
{ | ||
if (!IsReleaseMode) | ||
{ | ||
//only track in prod | ||
return; | ||
} | ||
|
||
try | ||
{ | ||
var executingAssembly = Assembly.GetExecutingAssembly(); | ||
var properties = new Dictionary<string, object> | ||
{ | ||
{ "distinct_id", "undefined" }, | ||
{ "server_id", "no-account-server" }, | ||
{ "token", MIXPANEL_TOKEN }, | ||
{ "hostApp", _hostApplication }, | ||
{ "hostAppVersion", _hostApplicationVersion }, | ||
{ | ||
"core_version", | ||
FileVersionInfo.GetVersionInfo(executingAssembly.Location).ProductVersion | ||
?? executingAssembly.GetName().Version.ToString() | ||
}, | ||
{ "$os", GetOs() } | ||
}; | ||
|
||
if (isAction) | ||
{ | ||
properties.Add("type", "action"); | ||
} | ||
|
||
if (customProperties != null) | ||
{ | ||
foreach (KeyValuePair<string, object> customProp in customProperties) | ||
{ | ||
properties[customProp.Key] = customProp.Value; | ||
} | ||
} | ||
|
||
string json = _serializer.Serialize(new { @event = eventName.ToString(), properties }); | ||
|
||
var query = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("data=" + HttpUtility.UrlEncode(json)))); | ||
|
||
using HttpClient client = new(); | ||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain")); | ||
query.Headers.ContentType = new MediaTypeHeaderValue("application/json"); | ||
var res = await client.PostAsync(MIXPANEL_SERVER + "/track?ip=1", query).ConfigureAwait(false); | ||
res.EnsureSuccessStatusCode(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
_eventEmitter.EmitError( | ||
new LoggingEventArgs( | ||
$"An exception was thrown in class {nameof(AnalyticsWithoutDependencies)} while attempting to record analytics", | ||
ex | ||
) | ||
); | ||
} | ||
} | ||
|
||
public void TrackEvent(object sender, ActionEventArgs args) | ||
{ | ||
_ = Enum.TryParse(args.EventName, out Analytics.Events eventName); | ||
TrackEvent(eventName, args.EventProperties); | ||
} | ||
|
||
private static string GetOs() | ||
{ | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
{ | ||
return "Windows"; | ||
} | ||
|
||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
{ | ||
return "Mac OS X"; | ||
} | ||
|
||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||
{ | ||
return "Linux"; | ||
} | ||
|
||
return "Unknown"; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Default Mixpanel events | ||
/// </summary> | ||
public enum Events | ||
AlanRynne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
/// <summary> | ||
/// Event triggered when data is sent to a Speckle Server | ||
/// </summary> | ||
Send, | ||
|
||
/// <summary> | ||
/// Event triggered when data is received from a Speckle Server | ||
/// </summary> | ||
Receive, | ||
|
||
/// <summary> | ||
/// Event triggered when a node is executed in a visual programming environment, it should contain the name of the action and the host application | ||
/// </summary> | ||
NodeRun, | ||
|
||
/// <summary> | ||
/// Event triggered when an action is executed in Desktop UI, it should contain the name of the action and the host application | ||
/// </summary> | ||
DUIAction, | ||
|
||
/// <summary> | ||
/// Event triggered when a node is first created in a visual programming environment, it should contain the name of the action and the host application | ||
/// </summary> | ||
NodeCreate, | ||
|
||
/// <summary> | ||
/// Event triggered when the import/export alert is launched or closed | ||
/// </summary> | ||
ImportExportAlert, | ||
|
||
/// <summary> | ||
/// Event triggered when the connector is registered | ||
/// </summary> | ||
Registered, | ||
|
||
/// <summary> | ||
/// Event triggered by the Mapping Tool | ||
/// </summary> | ||
MappingsAction | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
ConnectorCore/DllConflictManagement/AssemblyConflictInfoDto.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using System.Reflection; | ||
|
||
namespace Speckle.DllConflictManagement; | ||
|
||
/// <summary> | ||
/// Some system object types (System.Type, System.Reflection.AssemblyName, System.Reflection.Assembly) were throwing | ||
/// when I was attempting to serialize the <see cref="AssemblyConflictInfo"/> objects to be sent to mixpanel. | ||
/// Therefore I made this DTO to make an object that would hold the info we need and would be serializable. | ||
/// </summary> | ||
public sealed class AssemblyConflictInfoDto | ||
{ | ||
public AssemblyConflictInfoDto( | ||
AssemblyName speckleDependencyAssemblyName, | ||
AssemblyName conflictingAssembly, | ||
string folderName, | ||
string fullPath | ||
) | ||
{ | ||
ConflictingAssemblyName = conflictingAssembly.Name; | ||
ConflictingAssemblyVersion = conflictingAssembly.Version.ToString(); | ||
SpeckleExpectedVersion = speckleDependencyAssemblyName.Version.ToString(); | ||
FolderName = folderName; | ||
Path = fullPath; | ||
} | ||
|
||
public string SpeckleExpectedVersion { get; } | ||
public string ConflictingAssemblyName { get; } | ||
public string ConflictingAssemblyVersion { get; } | ||
public string FolderName { get; } | ||
public string Path { get; } | ||
} | ||
|
||
public static class AssemblyConflictInfoExtensions | ||
{ | ||
public static AssemblyConflictInfoDto ToDto(this AssemblyConflictInfo assemblyConflictInfo) | ||
{ | ||
return new( | ||
assemblyConflictInfo.SpeckleDependencyAssemblyName, | ||
assemblyConflictInfo.ConflictingAssembly.GetName(), | ||
assemblyConflictInfo.GetConflictingExternalAppName(), | ||
GetPathFromAutodeskOrFullPath(assemblyConflictInfo.ConflictingAssembly.Location) | ||
); | ||
} | ||
|
||
public static IEnumerable<AssemblyConflictInfoDto> ToDtos( | ||
this IEnumerable<AssemblyConflictInfo> assemblyConflictInfos | ||
) | ||
{ | ||
foreach (var assemblyConflictInfo in assemblyConflictInfos) | ||
{ | ||
yield return assemblyConflictInfo.ToDto(); | ||
} | ||
} | ||
|
||
private static readonly string[] separator = new[] { "Autodesk" }; | ||
|
||
private static string GetPathFromAutodeskOrFullPath(string fullPath) | ||
{ | ||
string[] splitOnAutodesk = fullPath.Split(separator, StringSplitOptions.None); | ||
|
||
if (splitOnAutodesk.Length == 2) | ||
{ | ||
return splitOnAutodesk[1]; | ||
} | ||
return fullPath; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Autodesk specific? 🤔 Ideally this project should work on any connector. Not a deal breaker, just found it weird. |
||
} |
2 changes: 1 addition & 1 deletion
2
...Management/DllConflictManagmentOptions.cs → ...entOptions/DllConflictManagmentOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting... could we do something like
IsFatal
here (though not directly that as it would require referencing something else)