Skip to content
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

feat: Support for SharePoint (Viva) Adaptive Card Extension #6695

Merged
merged 17 commits into from
Nov 1, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.SharePoint;
using Microsoft.Bot.Schema.Teams;
using Newtonsoft.Json.Linq;

namespace Microsoft.Bot.Builder.SharePoint
{
/// <summary>
/// The SharePointActivityHandler is derived from ActivityHandler. It adds support for
/// the SharePoint specific events and interactions.
/// </summary>
public class SharePointActivityHandler : ActivityHandler
{
/// <summary>
/// Invoked when an invoke activity is received from the connector.
/// Invoke activities can be used to communicate many different things.
/// </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>
/// <remarks>
/// Invoke activities communicate programmatic commands from a client or channel to a bot.
/// The meaning of an invoke activity is defined by the <see cref="IInvokeActivity.Name"/> property,
/// which is meaningful within the scope of a channel.
/// </remarks>
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
try
{
if (turnContext.Activity.Name == null)
{
throw new NotSupportedException();
}
else
{
switch (turnContext.Activity.Name)
{
case "cardExtension/getCardView":
return CreateInvokeResponse(await OnSharePointTaskGetCardViewAsync(turnContext, SafeCast<AceRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

case "cardExtension/getQuickView":
return CreateInvokeResponse(await OnSharePointTaskGetQuickViewAsync(turnContext, SafeCast<AceRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

case "cardExtension/getPropertyPaneConfiguration":
return CreateInvokeResponse(await OnSharePointTaskGetPropertyPaneConfigurationAsync(turnContext, SafeCast<AceRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));

case "cardExtension/setPropertyPaneConfiguration":
BaseHandleActionResponse setPropPaneConfigResponse = await OnSharePointTaskSetPropertyPaneConfigurationAsync(turnContext, SafeCast<AceRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false);
ValidateSetPropertyPaneConfigurationResponse(setPropPaneConfigResponse);
return CreateInvokeResponse(setPropPaneConfigResponse);

case "cardExtension/handleAction":
return CreateInvokeResponse(await OnSharePointTaskHandleActionAsync(turnContext, SafeCast<AceRequest>(turnContext.Activity.Value), cancellationToken).ConfigureAwait(false));
}
}
}
catch (InvokeResponseException e)
{
return e.CreateInvokeResponse();
}

return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Override this in a derived class to provide logic for when a card view is fetched.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="aceRequest">The ACE invoke request value payload.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A Card View Response for the request.</returns>
protected virtual Task<CardViewResponse> OnSharePointTaskGetCardViewAsync(ITurnContext<IInvokeActivity> turnContext, AceRequest aceRequest, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Override this in a derived class to provide logic for when a quick view is fetched.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="aceRequest">The ACE invoke request value payload.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A Quick View Response for the request.</returns>
protected virtual Task<QuickViewResponse> OnSharePointTaskGetQuickViewAsync(ITurnContext<IInvokeActivity> turnContext, AceRequest aceRequest, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Override this in a derived class to provide logic for getting configuration pane properties.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="aceRequest">The ACE invoke request value payload.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A Property Pane Configuration Response for the request.</returns>
protected virtual Task<GetPropertyPaneConfigurationResponse> OnSharePointTaskGetPropertyPaneConfigurationAsync(ITurnContext<IInvokeActivity> turnContext, AceRequest aceRequest, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Override this in a derived class to provide logic for setting configuration pane properties.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="aceRequest">The ACE invoke request value payload.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>Card view or no-op action response.</returns>
/// <remarks>The handler will fail with 500 status code if the response is of type <see cref="QuickViewHandleActionResponse" />.</remarks>
protected virtual Task<BaseHandleActionResponse> OnSharePointTaskSetPropertyPaneConfigurationAsync(ITurnContext<IInvokeActivity> turnContext, AceRequest aceRequest, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Override this in a derived class to provide logic for handling ACE actions.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="aceRequest">The ACE invoke request value payload.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A handle action response.</returns>
protected virtual Task<BaseHandleActionResponse> OnSharePointTaskHandleActionAsync(ITurnContext<IInvokeActivity> turnContext, AceRequest aceRequest, CancellationToken cancellationToken)
{
throw new InvokeResponseException(HttpStatusCode.NotImplemented);
}

/// <summary>
/// Safely casts an object to an object of type <typeparamref name="T"/> .
/// </summary>
/// <param name="value">The object to be casted.</param>
/// <returns>The object casted in the new type.</returns>
private static T SafeCast<T>(object value)
{
var obj = value as JObject;
if (obj == null)
{
throw new InvokeResponseException(HttpStatusCode.BadRequest, $"expected type '{value.GetType().Name}'");
}

return obj.ToObject<T>();
}

private void ValidateSetPropertyPaneConfigurationResponse(BaseHandleActionResponse response)
{
if (response is QuickViewHandleActionResponse)
{
throw new InvokeResponseException(HttpStatusCode.InternalServerError, "Response for SetPropertyPaneConfiguration action can't be of QuickView type.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AdaptiveCards" Version="1.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
95 changes: 95 additions & 0 deletions libraries/Microsoft.Bot.Schema/SharePoint/AceData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;

namespace Microsoft.Bot.Schema.SharePoint
{
/// <summary>
/// SharePoint Ace Data object.
/// </summary>
public class AceData
{
/// <summary>
/// Initializes a new instance of the <see cref="AceData"/> class.
/// </summary>
public AceData()
{
// Do nothing
}

/// <summary>
/// This enum contains the different types of card templates available in the SPFx framework.
/// </summary>
public enum AceCardSize
{
/// <summary>
/// Medium
/// </summary>
Medium,

/// <summary>
/// Large
/// </summary>
Large
}

/// <summary>
/// Gets or Sets the card size of the adaptive card extension of type <see cref="AceCardSize"/> enum.
/// </summary>
/// <value>This value is the size of the adaptive card extension.</value>
[JsonProperty(PropertyName = "cardSize")]
[JsonConverter(typeof(StringEnumConverter))]
public AceCardSize CardSize { get; set; }

/// <summary>
/// Gets or Sets the version of the data of type <see cref="string"/>.
/// </summary>
/// <value>This value is the version of the adaptive card extension.</value>
/// <remarks>Although there is no restriction on the format of this property, it is recommended to use semantic versioning.</remarks>
[JsonProperty(PropertyName = "dataVersion")]
public string DataVersion { get; set; }

/// <summary>
/// Gets or Sets the unique id (Guid) of type <see cref="string"/>.
/// </summary>
/// <value>This value is the ID of the adaptive card extension.</value>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }

/// <summary>
/// Gets or Sets the title of type <see cref="string"/>.
/// </summary>
/// <value>This value is the title of the adaptive card extension.</value>
[JsonProperty(PropertyName = "title")]
public string Title { get; set; }

/// <summary>
/// Gets or Sets the description of type <see cref="string"/>.
/// </summary>
/// <value>This value is the description of the adaptive card extension.</value>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }

/// <summary>
/// Gets or Sets the icon property of type <see cref="string"/>.
/// </summary>
/// <value>This value is the icon of the adaptive card extension.</value>
[JsonProperty(PropertyName = "iconProperty")]
public string IconProperty { get; set; }

/// <summary>
/// Gets or Sets the property bag of type <see cref="JObject"/>.
/// </summary>
/// <value>This value is the property bag of the adaptive card extension.</value>
[JsonProperty(PropertyName = "properties")]
#pragma warning disable CA2227
public JObject Properties { get; set; }
}
}
48 changes: 48 additions & 0 deletions libraries/Microsoft.Bot.Schema/SharePoint/AceRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.SharePoint
{
/// <summary>
/// ACE invoke request payload.
/// </summary>
public class AceRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="AceRequest"/> class.
/// </summary>
public AceRequest()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AceRequest"/> class.
/// </summary>
/// <param name="data">ACE request data.</param>
/// <param name="properties">ACE properties data.</param>
public AceRequest(object data = default, object properties = default)
{
Data = data;
Properties = properties;
}

/// <summary>
/// Gets or sets user ACE request data.
/// </summary>
/// <value>The ACE request data.</value>
[JsonProperty(PropertyName = "data")]
public object Data { get; set; }

/// <summary>
/// Gets or sets ACE properties data. Free payload with key-value pairs.
/// </summary>
/// <value>ACE Properties object.</value>
[JsonProperty(PropertyName = "properties")]
public object Properties { get; set; }
}
}
28 changes: 28 additions & 0 deletions libraries/Microsoft.Bot.Schema/SharePoint/Actions/BaseAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.SharePoint
{
/// <summary>
/// Base Action.
/// </summary>
public class BaseAction
{
[JsonProperty(PropertyName = "type")]
private readonly string type;

/// <summary>
/// Initializes a new instance of the <see cref="BaseAction"/> class.
/// </summary>
/// <param name="actionType">Type of the action.</param>
protected BaseAction(string actionType)
{
this.type = actionType;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.SharePoint
{
/// <summary>
/// SharePoint Confirmation Dialog object.
/// </summary>
public class ConfirmationDialog
{
/// <summary>
/// Initializes a new instance of the <see cref="ConfirmationDialog"/> class.
/// </summary>
public ConfirmationDialog()
{
// Do nothing
}

/// <summary>
/// Gets or Sets the title of type <see cref="string"/>.
/// </summary>
/// <value>This value is the title to display.</value>
[JsonProperty(PropertyName = "title")]
public string Title { get; set; }

/// <summary>
/// Gets or Sets the message of type <see cref="string"/>.
/// </summary>
/// <value>This value is the message to display.</value>
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
}
}
Loading
Loading