Skip to content

Commit 7a21035

Browse files
committed
bubble events for specific commands and their descendants when proxied
1 parent 13a46e7 commit 7a21035

File tree

10 files changed

+106
-19
lines changed

10 files changed

+106
-19
lines changed

src/Microsoft.DotNet.Interactive.Tests/DirectiveTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Threading.Tasks;
1010
using FluentAssertions;
11+
using Microsoft.DotNet.Interactive.Commands;
1112
using Microsoft.DotNet.Interactive.CSharp;
1213
using Microsoft.DotNet.Interactive.Events;
1314
using Microsoft.DotNet.Interactive.Formatting;
@@ -313,4 +314,29 @@ public async Task New_directives_can_be_added_after_older_ones_have_been_evaluat
313314
oneWasCalled.Should().BeTrue();
314315
twoWasCalled.Should().BeTrue();
315316
}
317+
318+
[Fact]
319+
public async Task Results_of_commands_sent_within_directive_handlers_contain_expected_events()
320+
{
321+
var cSharpKernel = new CSharpKernel();
322+
323+
using var kernel = new CompositeKernel { cSharpKernel };
324+
325+
var directive = new Command("#!dostuff");
326+
327+
ReturnValueProduced returnValueProduced = null;
328+
329+
directive.SetHandler(async () =>
330+
{
331+
var result = await cSharpKernel.SendAsync(new SubmitCode("123"));
332+
333+
returnValueProduced = result.Events.OfType<ReturnValueProduced>().SingleOrDefault();
334+
});
335+
336+
kernel.AddDirective(directive);
337+
338+
await kernel.SendAsync(new SubmitCode("#!dostuff"));
339+
340+
returnValueProduced.Should().NotBeNull();
341+
}
316342
}

src/Microsoft.DotNet.Interactive.Tests/KernelCommandNestingTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,33 @@ public async Task Commands_sent_within_the_code_of_another_command_do_not_publis
124124
result.Events.Should().NotContain(e => e is ReturnValueProduced);
125125
}
126126

127+
[Fact]
128+
public async Task Commands_sent_via_API_within_a_split_submission_do_not_bubble_events()
129+
{
130+
using var kernel = new CompositeKernel
131+
{
132+
new CSharpKernel("cs1"),
133+
new CSharpKernel("cs2")
134+
};
135+
136+
var command = new SubmitCode($"""
137+
#!cs1
138+
using {typeof(Kernel).Namespace};
139+
using {typeof(KernelCommand).Namespace};
140+
var result = await Kernel.Root.SendAsync(new SubmitCode("123.Display();\n456", "cs2"));
141+
142+
#!cs2
143+
Console.WriteLine(789);
144+
""");
145+
146+
var result = await kernel.SendAsync(command);
147+
148+
using var _ = new AssertionScope();
149+
result.Events.Should().NotContainErrors();
150+
result.Events.Should().NotContain(e => e is DisplayedValueProduced);
151+
result.Events.Should().NotContain(e => e is ReturnValueProduced);
152+
}
153+
127154
[Fact]
128155
public async Task Commands_sent_within_the_code_of_another_command_publish_CommandSucceeded_to_the_inner_result()
129156
{

src/Microsoft.DotNet.Interactive/Commands/KernelCommand.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public abstract class KernelCommand : IEquatable<KernelCommand>
1414
{
1515
private KernelCommand _parent;
1616
private string _token;
17+
private List<KernelCommand> _childCommandsToBubbleEventsFrom;
1718

1819
protected KernelCommand(
1920
string targetKernelName = null)
@@ -23,7 +24,8 @@ protected KernelCommand(
2324
RoutingSlip = new CommandRoutingSlip();
2425
}
2526

26-
[JsonIgnore] public KernelCommandInvocation Handler { get; set; }
27+
[JsonIgnore]
28+
public KernelCommandInvocation Handler { get; set; }
2729

2830
[JsonIgnore]
2931
public KernelCommand Parent => _parent;
@@ -115,17 +117,55 @@ static string CreateRootToken()
115117
private static int _nextRootToken = 0;
116118
#endif
117119

118-
[JsonIgnore] internal SchedulingScope SchedulingScope { get; set; }
120+
[JsonIgnore]
121+
internal SchedulingScope SchedulingScope { get; set; }
119122

120-
[JsonIgnore] internal bool? ShouldPublishCompletionEvent { get; set; }
123+
[JsonIgnore]
124+
internal bool? ShouldPublishCompletionEvent { get; set; }
121125

122126
[JsonIgnore]
123127
public ParseResult KernelChooserParseResult { get; internal set; }
124128

125129
[JsonIgnore]
126130
public CommandRoutingSlip RoutingSlip { get; }
127131

128-
internal bool ShouldResultIncludeEventsFromChildren { get; set; }
132+
internal bool WasProxied { get; set; }
133+
134+
internal void ResultShouldIncludeEventsFrom(KernelCommand childCommand)
135+
{
136+
if (_childCommandsToBubbleEventsFrom is null)
137+
{
138+
_childCommandsToBubbleEventsFrom = new();
139+
}
140+
141+
_childCommandsToBubbleEventsFrom.Add(childCommand);
142+
}
143+
144+
internal bool ShouldResultIncludeEventsFrom(KernelCommand childCommand)
145+
{
146+
if (_childCommandsToBubbleEventsFrom is null)
147+
{
148+
return false;
149+
}
150+
151+
for (var i = 0; i < _childCommandsToBubbleEventsFrom.Count; i++)
152+
{
153+
var command = _childCommandsToBubbleEventsFrom[i];
154+
155+
if (command.Equals(childCommand))
156+
{
157+
return true;
158+
}
159+
160+
if (command.WasProxied &&
161+
command.IsSelfOrDescendantOf(this))
162+
{
163+
return true;
164+
}
165+
}
166+
167+
return false;
168+
}
129169

130170
public virtual Task InvokeAsync(KernelInvocationContext context)
131171
{

src/Microsoft.DotNet.Interactive/Commands/RequestKernelInfo.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System;
5-
using System.Diagnostics;
65
using System.Text.Json.Serialization;
76

87
namespace Microsoft.DotNet.Interactive.Commands;

src/Microsoft.DotNet.Interactive/CompositeKernel.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,12 @@ private protected override async Task HandleRequestKernelInfoAsync(
190190
{
191191
context.Publish(new KernelInfoProduced(KernelInfo, command));
192192

193-
command.ShouldResultIncludeEventsFromChildren = true;
194-
195193
foreach (var childKernel in ChildKernels)
196194
{
197195
if (childKernel.SupportsCommand(command))
198196
{
199197
var childCommand = new RequestKernelInfo(childKernel.Name);
198+
command.ResultShouldIncludeEventsFrom(childCommand);
200199
childCommand.SetParent(command);
201200
childCommand.RoutingSlip.ContinueWith(command.RoutingSlip);
202201
await childKernel.HandleAsync(childCommand, context);

src/Microsoft.DotNet.Interactive/Connection/ProxyKernel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ private void UpdateKernelInfoFromEvent(KernelInfoProduced kernelInfoProduced)
7373

7474
private Task HandleByForwardingToRemoteAsync(KernelCommand command, KernelInvocationContext context)
7575
{
76+
command.WasProxied = true;
77+
7678
if (command.OriginUri is null)
7779
{
7880
if (context.HandlingKernel == this)
@@ -98,6 +100,7 @@ private Task HandleByForwardingToRemoteAsync(KernelCommand command, KernelInvoca
98100
return Task.CompletedTask;
99101
}
100102
}
103+
101104
var targetKernelName = command.TargetKernelName;
102105
if (command.TargetKernelName == Name)
103106
{
@@ -127,7 +130,6 @@ private Task HandleByForwardingToRemoteAsync(KernelCommand command, KernelInvoca
127130
{
128131
command.TargetKernelName = targetKernelName;
129132

130-
131133
if (te.Result is CommandFailed cf)
132134
{
133135
context.Fail(command, cf.Exception, cf.Message);

src/Microsoft.DotNet.Interactive/Kernel.Static.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ public static void Javascript(string scriptContent)
112112

113113
Task.Run(async () =>
114114
{
115-
var kernelCommand = new DisplayValue(formatted);
116-
context.Command.ShouldResultIncludeEventsFromChildren = true;
117-
await kernel.SendAsync(kernelCommand);
115+
var displayValue = new DisplayValue(formatted);
116+
context.Command.ResultShouldIncludeEventsFrom(displayValue);
117+
await kernel.SendAsync(displayValue);
118118
}).Wait(context.CancellationToken);
119119
}
120120

src/Microsoft.DotNet.Interactive/Kernel.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,6 @@ private bool TrySplitCommand(
161161
return false;
162162
}
163163

164-
originalCommand.ShouldResultIncludeEventsFromChildren = true;
165-
166164
commands = new[] { adjustedCommand };
167165
break;
168166

@@ -171,13 +169,11 @@ private bool TrySplitCommand(
171169
break;
172170
}
173171

174-
// FIX: (TrySplitCommand)
175-
176172
foreach (var command in commands)
177173
{
178174
if (!command.Equals(originalCommand))
179175
{
180-
originalCommand.ShouldResultIncludeEventsFromChildren = true;
176+
originalCommand.ResultShouldIncludeEventsFrom(command);
181177
}
182178

183179
var handlingKernel = GetHandlingKernel(command, context);

src/Microsoft.DotNet.Interactive/KernelCommandResult.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ public KernelCommandResult(KernelCommand command)
2424

2525
internal void AddEvent(KernelEvent @event)
2626
{
27-
// FIX: (AddEvent)
2827
if (!@event.Command.Equals(Command))
2928
{
30-
if (!Command.ShouldResultIncludeEventsFromChildren)
29+
if (!Command.ShouldResultIncludeEventsFrom(@event.Command))
3130
{
3231
return;
3332
}

src/Microsoft.DotNet.Interactive/KernelInvocationContext.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7-
using System.ComponentModel;
87
using System.Reactive.Linq;
98
using System.Reactive.Subjects;
109
using System.Threading;

0 commit comments

Comments
 (0)