Skip to content

Commit

Permalink
[Copilot] Copilot capabilities can depend on privacy notices (#2443)
Browse files Browse the repository at this point in the history
#### Summary
In addition to using AOAI, Copilot capabilities may need to integrate
with other services that depend on a privacy notice.
This PR adds a new event capabilities can subscribe to which provide a
list of required privacy notices that must be accepted before the
capability can be enabled.

#### Work Item(s)
Fixes
[AB#502565](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/502565)
  • Loading branch information
msft-sam authored Dec 4, 2024
1 parent 719efbc commit 7e54fcb
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ page 7770 "Copilot Cap. Early Preview"
Editable = false;
Width = 30;
}
field(Status; Rec.Status)
field(Status; Rec.EvaluateStatus())
{
ApplicationArea = All;
Caption = 'Status';
Expand Down Expand Up @@ -90,6 +90,8 @@ page 7770 "Copilot Cap. Early Preview"
trigger OnAction()
begin
if Dialog.Confirm(ActivateEarlyPreviewTxt, false) then begin
if not Rec.EnsurePrivacyNoticesApproved() then
exit;
Rec.Status := Rec.Status::Active;
Rec.Modify(true);

Expand Down Expand Up @@ -169,17 +171,19 @@ page 7770 "Copilot Cap. Early Preview"

local procedure SetStatusStyle()
begin
if (Rec.Status = Rec.Status::Active) then
if (Rec.EvaluateStatus() = Rec.Status::Active) then
StatusStyleExpr := 'Favorable'
else
StatusStyleExpr := '';
end;

local procedure SetActionsEnabled()
var
CopilotCapability: Codeunit "Copilot Capability";
begin
if CopilotCapabilityImpl.IsAdmin() then begin
ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled;
CapabilityEnabled := Rec.Status = Rec.Status::Active;
CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id");
end
else begin
ActionsEnabled := false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ page 7774 "Copilot Capabilities GA"
Editable = false;
Width = 30;
}
field(Status; Rec.Status)
field(Status; Rec.EvaluateStatus())
{
ApplicationArea = All;
Caption = 'Status';
Expand Down Expand Up @@ -89,6 +89,9 @@ page 7774 "Copilot Capabilities GA"

trigger OnAction()
begin
if not Rec.EnsurePrivacyNoticesApproved() then
exit;

Rec.Status := Rec.Status::Active;
Rec.Modify(true);

Expand Down Expand Up @@ -179,17 +182,19 @@ page 7774 "Copilot Capabilities GA"

local procedure SetStatusStyle()
begin
if (Rec.Status = Rec.Status::Active) then
if (Rec.EvaluateStatus() = Rec.Status::Active) then
StatusStyleExpr := 'Favorable'
else
StatusStyleExpr := '';
end;

local procedure SetActionsEnabled()
var
CopilotCapability: Codeunit "Copilot Capability";
begin
if CopilotCapabilityImpl.IsAdmin() then begin
ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled;
CapabilityEnabled := Rec.Status = Rec.Status::Active;
CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id");
end
else begin
ActionsEnabled := false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ page 7773 "Copilot Capabilities Preview"
Editable = false;
Width = 30;
}
field(Status; Rec.Status)
field(Status; Rec.EvaluateStatus())
{
ApplicationArea = All;
Caption = 'Status';
Expand Down Expand Up @@ -89,6 +89,9 @@ page 7773 "Copilot Capabilities Preview"

trigger OnAction()
begin
if not Rec.EnsurePrivacyNoticesApproved() then
exit;

Rec.Status := Rec.Status::Active;
Rec.Modify(true);

Expand Down Expand Up @@ -166,17 +169,19 @@ page 7773 "Copilot Capabilities Preview"

local procedure SetStatusStyle()
begin
if (Rec.Status = Rec.Status::Active) then
if (Rec.EvaluateStatus() = Rec.Status::Active) then
StatusStyleExpr := 'Favorable'
else
StatusStyleExpr := '';
end;

local procedure SetActionsEnabled()
var
CopilotCapability: Codeunit "Copilot Capability";
begin
if CopilotCapabilityImpl.IsAdmin() then begin
ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled;
CapabilityEnabled := Rec.Status = Rec.Status::Active;
CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id");
end
else begin
ActionsEnabled := false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,15 @@ codeunit 7773 "Copilot Capability"
begin
exit(CopilotCapabilityImpl.IsCapabilityActive(CopilotCapability, AppId));
end;

/// <summary>
/// Event that is raised to specify if a Copilot capability depends on any additional privacy notices.
/// </summary>
/// <param name="CopilotCapability">The capability.</param>
/// <param name="AppId">The app id associated with the capability.</param>
/// <param name="RequiredPrivacyNotices">The list of required privacy notices for the capability to be enabled.</param>
[IntegrationEvent(false, false)]
procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]])
begin
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,27 @@ codeunit 7774 "Copilot Capability Impl"
procedure IsCapabilityActive(CopilotCapability: Enum "Copilot Capability"; AppId: Guid): Boolean
var
CopilotSettings: Record "Copilot Settings";
CopilotCapabilityCU: Codeunit "Copilot Capability";
PrivacyNotice: Codeunit "Privacy Notice";
RequiredPrivacyNotices: List of [Code[50]];
RequiredPrivacyNotice: Code[50];
begin
CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted);
CopilotSettings.SetLoadFields(Status);
if not CopilotSettings.Get(CopilotCapability, AppId) then
exit(false);

exit(CopilotSettings.Status = Enum::"Copilot Status"::Active);
CopilotCapabilityCU.OnGetRequiredPrivacyNotices(CopilotCapability, AppId, RequiredPrivacyNotices);

if (CopilotSettings.Status <> Enum::"Copilot Status"::Active) or (RequiredPrivacyNotices.Count() <= 0) then
exit(CopilotSettings.Status = Enum::"Copilot Status"::Active);

// check privacy notices
foreach RequiredPrivacyNotice in RequiredPrivacyNotices do
if (PrivacyNotice.GetPrivacyNoticeApprovalState(RequiredPrivacyNotice, true) <> Enum::"Privacy Notice Approval State"::Agreed) then
exit(false);

exit(true);
end;

procedure SendActivateTelemetry(CopilotCapability: Enum "Copilot Capability"; AppId: Guid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace System.AI;

using System.Environment;
using System.Privacy;

codeunit 7760 "Copilot Capability Install"
{
Expand Down Expand Up @@ -53,4 +54,22 @@ codeunit 7760 "Copilot Capability Install"
begin
RegisterCapabilities();
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Copilot Capability", 'OnGetRequiredPrivacyNotices', '', false, false)]
local procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]])
var
SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg.";
ModuleInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(ModuleInfo);

if AppId <> ModuleInfo.Id then
exit;

if CopilotCapability <> Enum::"Copilot Capability"::Chat then
exit;

if not RequiredPrivacyNotices.Contains(SystemPrivacyNoticeReg.GetMicrosoftLearnID()) then
RequiredPrivacyNotices.Add(SystemPrivacyNoticeReg.GetMicrosoftLearnID());
end;
}
34 changes: 34 additions & 0 deletions src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace System.AI;

using System.Privacy;

/// <summary>
/// Table to keep track of each Copilot Capability settings.
/// </summary>
Expand Down Expand Up @@ -51,4 +53,36 @@ table 7775 "Copilot Settings"
Clustered = true;
}
}

procedure EvaluateStatus(): Enum "Copilot Status"
var
CopilotCapability: Codeunit "Copilot Capability";
begin
if Rec.Status <> Rec.Status::Active then
exit(Rec.Status);

if CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id") then
exit(Rec.Status::Active)
else
exit(Rec.Status::Inactive);
end;

procedure EnsurePrivacyNoticesApproved(): Boolean
var
CopilotCapability: Codeunit "Copilot Capability";
PrivacyNotice: Codeunit "Privacy Notice";
RequiredPrivacyNotices: List of [Code[50]];
RequiredPrivacyNotice: Code[50];
begin
CopilotCapability.OnGetRequiredPrivacyNotices(Rec.Capability, Rec."App Id", RequiredPrivacyNotices);

if RequiredPrivacyNotices.Count() <= 0 then
exit(true);

foreach RequiredPrivacyNotice in RequiredPrivacyNotices do
if not PrivacyNotice.ConfirmPrivacyNoticeApproval(RequiredPrivacyNotice, true) then
exit(false);

exit(true);
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ page 1565 "Privacy Notices"

trigger OnAfterGetRecord()
begin
Rec.CalcFields(Rec.Enabled, Rec.Disabled);
Accepted := Rec.Enabled;
Rejected := Rec.Disabled;
UserDecides := not (Accepted or Rejected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

namespace System.Privacy;

/// <summary>
/// This codeunit registers platform level privacy notices and provides procedures to consistently reference the privacy notices.
/// </summary>
codeunit 1566 "System Privacy Notice Reg."
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

Expand All @@ -17,11 +19,6 @@ codeunit 1566 "System Privacy Notice Reg."
PowerAutomateLabelTxt: Label 'Microsoft Power Automate', Locked = true; // Product names are not translated and it's important this entry exists.
MicrosoftLearnTxt: Label 'Microsoft Learn', Locked = true; // Product names are not translated and it's important this entry exists.

procedure GetMicrosoftLearnID(): Text
begin
exit(MicrosoftLearnTxt);
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", OnRegisterPrivacyNotices, '', false, false)]
local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary)
begin
Expand All @@ -37,16 +34,37 @@ codeunit 1566 "System Privacy Notice Reg."
if not TempPrivacyNotice.Insert() then;
end;

/// <summary>
/// Gets the Microsoft Learn privacy notice identifier.
/// </summary>
/// <returns>The privacy notice id for Microsoft Learn.</returns>
procedure GetMicrosoftLearnID(): Code[50]
begin
exit(MicrosoftLearnTxt);
end;

/// <summary>
/// Gets the Microsoft Teams privacy notice identifier.
/// </summary>
/// <returns>The privacy notice id for Microsoft Teams.</returns>
procedure GetTeamsPrivacyNoticeId(): Code[50]
begin
exit(MicrosoftTeamsTxt);
end;

/// <summary>
/// Gets the Power Automate privacy notice identifier.
/// </summary>
/// <returns>The privacy notice id for Power Automate.</returns>
procedure GetPowerAutomatePrivacyNoticeId(): Code[50]
begin
exit(PowerAutomateIdTxt);
end;

/// <summary>
/// Gets the Power Automate privacy notice name.
/// </summary>
/// <returns>The privacy notice name for Power Automate.</returns>
procedure GetPowerAutomatePrivacyNoticeName(): Code[250]
begin
exit(PowerAutomateLabelTxt);
Expand Down

0 comments on commit 7e54fcb

Please sign in to comment.