Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
30 changes: 30 additions & 0 deletions src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,9 @@ private void OnPythonEngineUpgradeToastRequested(string msg, bool stayOpen, stri
/// <returns>True if the control was added, false if it already existed</returns>
internal bool AddOrFocusExtensionControl(IViewExtension viewExtension, UIElement content)
{
if (DisableExtensionWhenNoNetworkMode(viewExtension.UniqueId, viewExtension.Name, "opened"))
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Returns false when the extension is blocked, but returning false is actually an indication that the extension is already present and the caller AddToExtensionsSideBar treats it as such and will log ExtensionAlreadyPresent for a blocked extension. ... This might require a switch from a bool to an enum. It is an internal method and would not be a breaking change.


var window = ExtensionWindows.ContainsKey(viewExtension.Name) ? ExtensionWindows[viewExtension.Name] : null;
var tab = FindExtensionTab(viewExtension);
var addExtensionControl = window == null && tab == null;
Expand Down Expand Up @@ -1019,6 +1022,10 @@ private void ExtensionWindow_Closed(object sender, EventArgs e)
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.HideOrShowRightSideBar(e.Action);
if (e.Action == NotifyCollectionChangedAction.Add)
{
DisableExtensionTabsWhenIDSDKNotInitialized();
}
}

private TabItem FindExtensionTab(IViewExtension viewExtension)
Expand Down Expand Up @@ -1422,6 +1429,7 @@ private void DynamoLoadedViewExtensionHandler(ViewLoadedParams loadedParams, IEn
Log(ext.Name + ": " + exc.Message);
}
}
DisableExtensionTabsWhenIDSDKNotInitialized();
EnsureGraphPropertiesBinding();
}

Expand Down Expand Up @@ -3414,5 +3422,27 @@ internal bool DisableExtensionWhenNoNetworkMode(string extensionId, string exten

return false;
}

/// <summary>
/// Disables (but keeps visible) the Autodesk Assistant and MCP sidebar tabs when IDSDK is
/// not initialized, preventing the cascade of native error dialogs that occur when the user
/// opens the Assistant without Autodesk Identity installed.
/// </summary>
internal void DisableExtensionTabsWhenIDSDKNotInitialized()
{
if (dynamoViewModel.Model.AuthenticationManager.IsIDSDKInitialized())
return;

foreach (var extensionId in new[] { AutodeskAssistantExtensionId, McpViewExtensionId })
{
var tab = dynamoViewModel.SideBarTabItems.OfType<TabItem>()
.SingleOrDefault(t => string.Equals(t.Uid, extensionId, StringComparison.OrdinalIgnoreCase));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SingleOrDefault() throws InvalidOperationException if two tabs ever share a Uid ... rare, but possible when re-adding a tab. Better to be safe and not assume uniqueness. An exception here can crash the UI.

if (tab != null)
{
tab.IsEnabled = false;
Log($"Extension tab {tab.Header} disabled because IDSDK is not initialized");
}
Comment on lines +3438 to +3444
}
}
}
}
192 changes: 192 additions & 0 deletions test/DynamoCoreWpfTests/DynamoViewNoNetworkModeTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using Dynamo.Controls;
using Dynamo.Core;
using Dynamo.Models;
using Dynamo.ViewModels;
using Dynamo.Wpf.Extensions;
using NUnit.Framework;
using TestServices;

Expand Down Expand Up @@ -100,5 +103,194 @@ public void UnrecognizedExtensionIsNotDisabledWhenNoNetworkModeIsEnabled()

Assert.IsFalse(shouldDisable);
}

[Test]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These tests assume the IDSDK is unavailable, but on a test machine or a dev machine, it might be installed and fail.

Add Assume.That(!...IsIDSDKInitialized()) or Assert.Ignore

public void AssistantAndMcpTabsAreDisabledWhenIDSDKIsNotInitialized()
{
var pathResolver = new TestPathResolver();
DynamoModel modelWithUninitializedIDSDK = null;
DynamoViewModel viewModelWithUninitializedIDSDK = null;
DynamoView viewWithUninitializedIDSDK = null;

try
{
// IDSDKManager.IsIDSDKInitialized returns false when the native IDSDK library
// is not installed (e.g. test environments, VMs without Autodesk Identity).
modelWithUninitializedIDSDK = DynamoModel.Start(new DynamoModel.DefaultStartConfiguration()
{
PathResolver = pathResolver,
StartInTestMode = true,
GeometryFactoryPath = preloader.GeometryFactoryPath,
ProcessMode = Dynamo.Scheduler.TaskProcessMode.Synchronous,
NoNetworkMode = false,
AuthProvider = new IDSDKManager()
});

Comment on lines +128 to +129
viewModelWithUninitializedIDSDK = DynamoViewModel.Start(new DynamoViewModel.StartConfiguration()
{
DynamoModel = modelWithUninitializedIDSDK
});

viewWithUninitializedIDSDK = new DynamoView(viewModelWithUninitializedIDSDK);

// Simulate the extension having loaded its tab into the sidebar.
// Adding to SideBarTabItems triggers CollectionChanged → DisableExtensionTabsWhenIDSDKNotInitialized automatically.
var assistantTab = new System.Windows.Controls.TabItem { Uid = DynamoView.AutodeskAssistantExtensionId };
var mcpTab = new System.Windows.Controls.TabItem { Uid = DynamoView.McpViewExtensionId };
viewModelWithUninitializedIDSDK.SideBarTabItems.Add(assistantTab);
viewModelWithUninitializedIDSDK.SideBarTabItems.Add(mcpTab);

Assert.IsFalse(assistantTab.IsEnabled);
Assert.IsFalse(mcpTab.IsEnabled);
}
finally
{
if (viewWithUninitializedIDSDK != null && viewWithUninitializedIDSDK.IsLoaded)
{
viewWithUninitializedIDSDK.Close();
}

if (viewModelWithUninitializedIDSDK != null)
{
var shutdownParams = new DynamoViewModel.ShutdownParams(shutdownHost: false, allowCancellation: false);
viewModelWithUninitializedIDSDK.PerformShutdownSequence(shutdownParams);
}
}
}

[Test]
public void AssistantTabRemainsDisabledWhenReAddedToSidebarAfterWorkspaceOpen()
{
var pathResolver = new TestPathResolver();
DynamoModel modelWithUninitializedIDSDK = null;
DynamoViewModel viewModelWithUninitializedIDSDK = null;
DynamoView viewWithUninitializedIDSDK = null;

try
{
modelWithUninitializedIDSDK = DynamoModel.Start(new DynamoModel.DefaultStartConfiguration()
{
PathResolver = pathResolver,
StartInTestMode = true,
GeometryFactoryPath = preloader.GeometryFactoryPath,
ProcessMode = Dynamo.Scheduler.TaskProcessMode.Synchronous,
NoNetworkMode = false,
AuthProvider = new IDSDKManager()
});

viewModelWithUninitializedIDSDK = DynamoViewModel.Start(new DynamoViewModel.StartConfiguration()
{
DynamoModel = modelWithUninitializedIDSDK
});

viewWithUninitializedIDSDK = new DynamoView(viewModelWithUninitializedIDSDK);

// Initial add — extension loads its tab during startup.
var assistantTab = new System.Windows.Controls.TabItem { Uid = DynamoView.AutodeskAssistantExtensionId };
viewModelWithUninitializedIDSDK.SideBarTabItems.Add(assistantTab);
Assert.IsFalse(assistantTab.IsEnabled, "Tab should be disabled on initial add");

// Simulate the extension removing and re-adding its tab (e.g. on workspace open).
viewModelWithUninitializedIDSDK.SideBarTabItems.Remove(assistantTab);
var reAddedTab = new System.Windows.Controls.TabItem { Uid = DynamoView.AutodeskAssistantExtensionId };
viewModelWithUninitializedIDSDK.SideBarTabItems.Add(reAddedTab);

Assert.IsFalse(reAddedTab.IsEnabled, "Re-added tab should still be disabled when IDSDK is not initialized");
}
finally
{
if (viewWithUninitializedIDSDK != null && viewWithUninitializedIDSDK.IsLoaded)
{
viewWithUninitializedIDSDK.Close();
}

if (viewModelWithUninitializedIDSDK != null)
{
var shutdownParams = new DynamoViewModel.ShutdownParams(shutdownHost: false, allowCancellation: false);
viewModelWithUninitializedIDSDK.PerformShutdownSequence(shutdownParams);
}
}
}

[Test]
public void AssistantAndMcpTabsAreNotDisabledWhenIDSDKIsInitialized()
{
var pathResolver = new TestPathResolver();
DynamoModel modelWithNullAuthProvider = null;
DynamoViewModel viewModelWithNullAuthProvider = null;
DynamoView viewWithNullAuthProvider = null;

try
{
// When AuthProvider is null (host environment or no IDSDK configured),
// IsIDSDKInitialized() returns true — tabs should remain enabled.
modelWithNullAuthProvider = DynamoModel.Start(new DynamoModel.DefaultStartConfiguration()
{
PathResolver = pathResolver,
StartInTestMode = true,
GeometryFactoryPath = preloader.GeometryFactoryPath,
ProcessMode = Dynamo.Scheduler.TaskProcessMode.Synchronous,
NoNetworkMode = false
});

viewModelWithNullAuthProvider = DynamoViewModel.Start(new DynamoViewModel.StartConfiguration()
{
DynamoModel = modelWithNullAuthProvider
});

viewWithNullAuthProvider = new DynamoView(viewModelWithNullAuthProvider);

var assistantTab = new System.Windows.Controls.TabItem { Uid = DynamoView.AutodeskAssistantExtensionId };
var mcpTab = new System.Windows.Controls.TabItem { Uid = DynamoView.McpViewExtensionId };
viewModelWithNullAuthProvider.SideBarTabItems.Add(assistantTab);
viewModelWithNullAuthProvider.SideBarTabItems.Add(mcpTab);

viewWithNullAuthProvider.DisableExtensionTabsWhenIDSDKNotInitialized();

Assert.IsTrue(assistantTab.IsEnabled);
Assert.IsTrue(mcpTab.IsEnabled);
}
finally
{
if (viewWithNullAuthProvider != null && viewWithNullAuthProvider.IsLoaded)
{
viewWithNullAuthProvider.Close();
}

if (viewModelWithNullAuthProvider != null)
{
var shutdownParams = new DynamoViewModel.ShutdownParams(shutdownHost: false, allowCancellation: false);
viewModelWithNullAuthProvider.PerformShutdownSequence(shutdownParams);
}
}
}

[Test]
public void AssistantTabCannotBeAddedViaSideBarWhenNoNetworkModeIsEnabled()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this testing NoNetworkMode?

{
// Simulates the code path where an extension calls AddToExtensionsSideBar() after
// initial load (e.g. from IExtensionStorageAccess.WorkspaceOpened), which bypasses
// the DisableExtensionWhenNoNetworkMode guard in DynamoLoadedViewExtensionHandler.
// AddOrFocusExtensionControl must block these late attempts in NoNetworkMode.
var stubExtension = new StubViewExtension(DynamoView.AutodeskAssistantExtensionId);
var added = View.AddOrFocusExtensionControl(stubExtension, null);

Assert.IsFalse(added);
Assert.IsFalse(ViewModel.SideBarTabItems
.OfType<System.Windows.Controls.TabItem>()
.Any(t => string.Equals(t.Uid, DynamoView.AutodeskAssistantExtensionId,
StringComparison.OrdinalIgnoreCase)));
}

private class StubViewExtension : IViewExtension
{
public StubViewExtension(string uniqueId) { UniqueId = uniqueId; }
public string UniqueId { get; }
public string Name => "Stub";
public void Startup(ViewStartupParams p) { }
public void Loaded(ViewLoadedParams p) { }
public void Shutdown() { }
public void Dispose() { }
}
}
}
Loading