Skip to content

Commit

Permalink
Add Teams feedback loop
Browse files Browse the repository at this point in the history
  • Loading branch information
sw-joelmut committed Nov 20, 2024
1 parent e449b3c commit 3e88d9a
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 4 deletions.
38 changes: 35 additions & 3 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext

case "task/submit":
return CreateInvokeResponse(await OnTeamsTaskModuleSubmitAsync(turnContext, SafeCast<TaskModuleRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

case "tab/fetch":
return CreateInvokeResponse(await OnTeamsTabFetchAsync(turnContext, SafeCast<TabRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

case "tab/submit":
return CreateInvokeResponse(await OnTeamsTabSubmitAsync(turnContext, SafeCast<TabSubmit>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

Expand All @@ -98,6 +98,13 @@ protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext
case "config/submit":
return CreateInvokeResponse(await OnTeamsConfigSubmitAsync(turnContext, turnContext.Activity.Value as JObject, cancellationToken).ConfigureAwait(false));

case "message/submitAction":
await OnTeamsMessageSubmitActionAsync(turnContext, SafeCast<FeedbackResponse>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false);
return CreateInvokeResponse();

case "message/fetchTask":
return CreateInvokeResponse(await OnTeamsMessageFetchTaskAsync(turnContext, cancellationToken).ConfigureAwait(false));

default:
return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
}
Expand Down Expand Up @@ -686,7 +693,7 @@ protected virtual Task OnTeamsChannelRenamedAsync(ChannelInfo channelInfo, TeamI
{
return Task.CompletedTask;
}

/// <summary>
/// Invoked when a Channel Restored event activity is received from the connector.
/// Channel Restored correspond to the user restoring a previously deleted channel.
Expand Down Expand Up @@ -994,6 +1001,31 @@ protected virtual Task OnTeamsMessageSoftDeleteAsync(ITurnContext<IMessageDelete
return Task.CompletedTask;
}

/// <summary>
/// Invoked when a feedback loop activity is received.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="feedback">A strongly-typed feedback loop object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected virtual Task OnTeamsMessageSubmitActionAsync(ITurnContext<IInvokeActivity> turnContext, FeedbackResponse feedback, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Invoked when a custom feedback loop activity is received to show either an AdaptiveCard or website.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected virtual Task<TaskModuleContinueResponse> OnTeamsMessageFetchTaskAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Safely casts an object to an object of type <typeparamref name="T"/> .
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/FeedbackInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Bot.Schema.Teams
{
using Newtonsoft.Json;

/// <summary>
/// Describes feedback loop information.
/// </summary>
public partial class FeedbackInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="FeedbackInfo"/> class.
/// </summary>
public FeedbackInfo()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="FeedbackInfo"/> class.
/// </summary>
/// <param name="type">Unique identifier representing a team.</param>
public FeedbackInfo(string type = FeedbackInfoTypes.Default)
{
Type = type;
CustomInit();
}

/// <summary>
/// Gets or sets the feedback loop type. Possible values include: 'default', 'custom'.
/// </summary>
/// <value>
/// The feedback loop type (see <see cref="FeedbackInfoTypes"/>).
/// </value>
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }

partial void CustomInit();
}
}
21 changes: 21 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/FeedbackInfoTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Bot.Schema.Teams
{
/// <summary>
/// Defines feedback loop type. Depending on the type, the feedback window will have a different structure.
/// </summary>
public static class FeedbackInfoTypes
{
/// <summary>
/// The type value for default feedback window form.
/// </summary>
public const string Default = "default";

/// <summary>
/// The type value for custom feedback window, can be either an AdaptiveCard or website.
/// </summary>
public const string Custom = "custom";
}
}
57 changes: 57 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/FeedbackResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Bot.Schema.Teams
{
using Newtonsoft.Json;

/// <summary>
/// Envelope for Feedback Response.
/// </summary>
public partial class FeedbackResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="FeedbackResponse"/> class.
/// </summary>
public FeedbackResponse()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="FeedbackResponse"/> class.
/// </summary>
/// <param name="actionName">Unique identifier representing a team.</param>
/// <param name="actionValue">Unique identifier representing a team2.</param>
/// <param name="replyToId">Unique identifier representing a team3.</param>
public FeedbackResponse(string actionName = default, FeedbackResponseActionValue actionValue = default, string replyToId = default)
{
ActionName = actionName;
ActionValue = actionValue;
ReplyToId = replyToId;
CustomInit();
}

/// <summary>
/// Gets or sets the action name.
/// </summary>
/// <value>Name of the action.</value>
public string ActionName { get; set; } = "feedback";

/// <summary>
/// Gets or sets the response for the action value.
/// </summary>
/// <value>The action value that contains the feedback reaction and message.</value>
[JsonProperty(PropertyName = "actionValue")]
public FeedbackResponseActionValue ActionValue { get; set; }

/// <summary>
/// Gets or sets the ID of the message to which this message is a reply.
/// </summary>
/// <value>Value of the ID to reply.</value>
[JsonProperty(PropertyName = "replyToId")]
public string ReplyToId { get; set; }

partial void CustomInit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Bot.Schema.Teams
{
using Newtonsoft.Json;

/// <summary>
/// Envelope for Feedback ActionValue Response.
/// </summary>
public partial class FeedbackResponseActionValue
{
/// <summary>
/// Initializes a new instance of the <see cref="FeedbackResponseActionValue"/> class.
/// </summary>
public FeedbackResponseActionValue()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="FeedbackResponseActionValue"/> class.
/// </summary>
/// <param name="reaction">The reaction of the feedback.</param>
/// <param name="feedback">The feedback content.</param>
public FeedbackResponseActionValue(string reaction = default, string feedback = default)
{
Reaction = reaction;
Feedback = feedback;
CustomInit();
}

/// <summary>
/// Gets or sets the reaction, either "like" or "dislike".
/// </summary>
/// <value>val.</value>
[JsonProperty(PropertyName = "reaction")]
public string Reaction { get; set; }

/// <summary>
/// Gets or sets the feedback content provided by the user when prompted with "What did you like/dislike?".
/// </summary>
/// <value>val.</value>
[JsonProperty(PropertyName = "feedback")]
public string Feedback { get; set; }

partial void CustomInit();
}
}
7 changes: 7 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/TeamsChannelData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ public TeamsChannelData(ChannelInfo channel = default, string eventType = defaul
[JsonProperty(PropertyName = "settings")]
public TeamsChannelDataSettings Settings { get; set; }

/// <summary>
/// Gets or sets information about custom feedback buttons.
/// </summary>
/// <value>The <see cref="FeedbackInfo"/> for this <see cref="TeamsChannelData"/>.</value>
[JsonProperty(PropertyName = "feedbackLoop")]
public FeedbackInfo FeedbackLoop { get; set; }

/// <summary>
/// Gets the OnBehalfOf list for user attribution.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,93 @@ void CaptureSend(Activity[] arg)
Assert.Equal(200, ((InvokeResponse)activitiesToSend[0].Value).Status);
}

[Fact]
public async Task TestMessageSubmitAction()
{
// Arrange
var activity = new Activity
{
Type = ActivityTypes.Invoke,
Name = "message/submitAction"
};

Activity[] activitiesToSend = null;
void CaptureSend(Activity[] arg)
{
activitiesToSend = arg;
}

var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity);

// Act
var bot = new TestActivityHandler();
await ((IBot)bot).OnTurnAsync(turnContext);

// Assert
Assert.NotNull(activitiesToSend);
Assert.Single(activitiesToSend);
Assert.IsType<InvokeResponse>(activitiesToSend[0].Value);
Assert.Equal(200, ((InvokeResponse)activitiesToSend[0].Value).Status);
}

[Fact]
public async Task TestMessageFetchTaskDefault()
{
// Arrange
var activity = new Activity
{
Type = ActivityTypes.Invoke,
Name = "message/fetchTask"
};

Activity[] activitiesToSend = null;
void CaptureSend(Activity[] arg)
{
activitiesToSend = arg;
}

var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity);

// Act
var bot = new TestActivityHandler();
await ((IBot)bot).OnTurnAsync(turnContext);

// Assert
Assert.NotNull(activitiesToSend);
Assert.Single(activitiesToSend);
Assert.IsType<InvokeResponse>(activitiesToSend[0].Value);
Assert.Equal(501, ((InvokeResponse)activitiesToSend[0].Value).Status);
}

[Fact]
public async Task TestMessageFetchTaskCustom()
{
// Arrange
var activity = new Activity
{
Type = ActivityTypes.Invoke,
Name = "message/fetchTask"
};

Activity[] activitiesToSend = null;
void CaptureSend(Activity[] arg)
{
activitiesToSend = arg;
}

var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity);

// Act
var bot = new TestActivityHandler();
await ((IBot)bot).OnTurnAsync(turnContext);

// Assert
Assert.NotNull(activitiesToSend);
Assert.Single(activitiesToSend);
Assert.IsType<InvokeResponse>(activitiesToSend[0].Value);
Assert.Equal(501, ((InvokeResponse)activitiesToSend[0].Value).Status);
}

private class TestActivityHandler : TeamsActivityHandler
{
}
Expand Down
Loading

0 comments on commit 3e88d9a

Please sign in to comment.