PageBackground task currency sample

This commit is contained in:
Damien Girard 2019-10-04 17:42:15 +03:00
Родитель cf4f0da8ff
Коммит e68e0bc997
13 изменённых файлов: 730 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,74 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
pageextension 50101 "Currencies PageExt" extends Currencies
{
layout
{
addlast(factboxes)
{
part("Latest rates"; "Latest Currency Rate Factbox")
{
ApplicationArea = Basic, Suite;
}
}
}
trigger OnAfterGetCurrRecord()
var
PbtParameters: Dictionary of [Text, Text];
Currency: Record Currency;
CurrenciesToRetrieve: Text;
DemoAlCurrencySetup: Record "Demo AL Currency Setup";
begin
if (PbtTaskId <> 0) then begin
// Reseting PbtTaskId to 0, to make sure that the completion trigger will not display data for the wrong record.
PbtTaskId := 0;
end;
CurrPage."Latest rates".Page.ResetTempTable(Code);
if (Code = '') then
exit;
Currency.SetFilter(Code, '<>' + Code);
if Currency.FindSet then begin
repeat
CurrenciesToRetrieve += Currency.Code + ',';
until Currency.Next = 0;
end;
CurrenciesToRetrieve := CurrenciesToRetrieve.TrimEnd(',');
PbtParameters.Set('Date', 'latest');
PbtParameters.Set('CurrencyBase', Code);
PbtParameters.Set('Currencies', CurrenciesToRetrieve);
DemoAlCurrencySetup.Get('SETUP');
PbtParameters.Set('SleepSimulation', Format(DemoAlCurrencySetup.SleepDurationFactbox)); // Delay to simulate a slow HTTP call
// Testability
OnBeforePageBackgroundTaskSchedule(PbtParameters);
// Default timeout is 2min, max is 10min.
CurrPage.EnqueueBackgroundTask(PbtTaskId, Codeunit::CurrencyRetriever, PbtParameters, 100000, PageBackgroundTaskErrorLevel::Warning);
end;
trigger OnPageBackgroundTaskCompleted(TaskId: Integer; Results: Dictionary of [Text, Text])
begin
if (TaskId = PbtTaskId) then begin
// Adding the current currency as 0 (so it looks better)
Results.Set(Code, '1.0');
CurrPage."Latest rates".Page.InitTempTable(Results);
end;
end;
// For testability of the page.
[IntegrationEvent(false, false)]
local procedure OnBeforePageBackgroundTaskSchedule(var PbtParameters: Dictionary of [Text, Text])
begin
end;
var
PbtTaskId: Integer;
}

Просмотреть файл

@ -0,0 +1,92 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
// Remark: This code doesn't re-use any of the currencies feature of Business Central for demo purpose.
codeunit 50100 CurrencyRetriever
{
trigger OnRun()
var
date: Text;
currencyBase: Text;
currencies: Text; // Commas separated list of currencies to get
results: Dictionary of [Text, Text];
jsonContentText: Text;
sleepSimulation: Integer;
begin
// Getting the page background tasks parameters
date := Page.GetBackgroundParameters().Get('Date');
currencyBase := Page.GetBackgroundParameters().Get('CurrencyBase');
currencies := Page.GetBackgroundParameters().Get('Currencies');
// For demo purpose of slow calls
if (Page.GetBackgroundParameters().ContainsKey('SleepSimulation')) then begin
Evaluate(sleepSimulation, Page.GetBackgroundParameters().Get('SleepSimulation'));
Sleep(sleepSimulation);
end;
// Getting the exchange rates.
// Remark: For testability, the Http request can be mocked.
if Page.GetBackgroundParameters().ContainsKey('MockHttpResponse') then
jsonContentText := Page.GetBackgroundParameters().Get('MockHttpResponse')
else
jsonContentText := GetExchangeRate(date, currencyBase, currencies);
// Parsing the exchange rates
ParseExchangeRate(results, jsonContentText);
// Setting the page background task result
Page.SetBackgroundTaskResult(results);
end;
// Parse exchange rates through exchangeratesapi.io
local procedure GetExchangeRate(Date: Text; CurrencyBase: Text; Currencies: Text): Text
var
client: HttpClient;
responseMessage: HttpResponseMessage;
jsonContentText: Text;
Url: Text;
RequestErr: Label 'An error occured when trying to get the exchange rates: \\%1:\\%2';
begin
Url := 'https://api.exchangeratesapi.io/' + Date + '?base=' + CurrencyBase + '&symbols=' + Currencies;
client.Get(Url, responseMessage);
if not responseMessage.IsSuccessStatusCode() then
Error(RequestErr, responseMessage.HttpStatusCode, responseMessage.ReasonPhrase);
responseMessage.Content.ReadAs(jsonContentText);
exit(jsonContentText);
end;
// Structure: {"rates":{"USD":decimal,"GBP":decimal},"base":"DKK","date":"2019-09-26"}
local procedure ParseExchangeRate(var results: Dictionary of [Text, Text]; jsonContentText: Text)
var
jsonObject: JsonObject;
jsonToken: JsonToken;
jsonArray: JsonArray;
i: Decimal;
currentCurrency: Text;
WrongFormatErr: Label 'The response is not formatted correctly.';
begin
if not jsonObject.ReadFrom(jsonContentText) then
Error(WrongFormatErr);
GetJsonToken(jsonObject, 'rates', jsonToken);
jsonObject := jsonToken.AsObject();
for i := 1 to jsonObject.Keys.Count do begin
currentCurrency := jsonObject.Keys.Get(i);
GetJsonToken(jsonObject, currentCurrency, jsonToken);
results.Set(currentCurrency, Format(jsonToken.AsValue().AsDecimal()));
end;
end;
local procedure GetJsonToken(JsonObject: JsonObject; KeyText: Text; VAR JsonToken: JsonToken)
var
CannotFindKeyErr: Label 'Cannot find the following key: %1';
begin
if not JsonObject.Get(KeyText, JsonToken) then
Error(CannotFindKeyErr, KeyText);
end;
}

Просмотреть файл

@ -0,0 +1,33 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
page 50100 "Demo AL Currency Setup"
{
PageType = Card;
ApplicationArea = All;
UsageCategory = Administration;
SourceTable = "Demo AL Currency Setup";
layout
{
area(Content)
{
group("Setup")
{
field(SleepDurationFactbox; SleepDurationFactbox)
{
ApplicationArea = All;
Caption = 'Factbox sleep duration in MS';
}
field(SleepDurationRoleCenter; SleepDurationRoleCenter)
{
ApplicationArea = All;
Caption = 'Role Center sleep duration in MS';
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
table 50101 "Demo AL Currency Setup"
{
DataClassification = SystemMetadata;
DataPerCompany = false;
fields
{
field(1; "Primary Key"; Code[10])
{
Caption = 'Primary Key';
}
field(2; SleepDurationRoleCenter; Integer)
{
DataClassification = SystemMetadata;
}
field(3; SleepDurationFactbox; Integer)
{
DataClassification = SystemMetadata;
}
}
keys
{
key("Primary Key"; "Primary Key")
{
Clustered = true;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
table 50100 "Latest Currency Rate"
{
DataClassification = SystemMetadata;
fields
{
field(1; Code; Code[10])
{
DataClassification = SystemMetadata;
}
field(2; Rate; Decimal)
{
DataClassification = SystemMetadata;
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
page 50103 "Latest Currency Rate Factbox"
{
SourceTable = "Latest Currency Rate";
SourceTableTemporary = true;
PageType = ListPart;
InsertAllowed = false;
DeleteAllowed = false;
ModifyAllowed = false;
LinksAllowed = false;
Editable = false;
RefreshOnActivate = false;
Caption = 'Latest currency rates';
layout
{
area(Content)
{
repeater(Rates)
{
Caption = 'Latest rates';
field(Code; Code)
{
ApplicationArea = Basic, Suite;
}
field(Rate; Rate)
{
ApplicationArea = Basic, Suite;
DecimalPlaces = 5;
}
}
}
}
procedure InitTempTable(Results: Dictionary of [Text, Text])
var
CodeKey: Text;
begin
foreach CodeKey in Results.Keys do begin
Code := CodeKey;
Evaluate(Rate, Results.Get(CodeKey));
if not Modify() then
Insert();
end;
CurrPage.Update(false);
end;
procedure ResetTempTable(CodeKey: Code[10])
begin
DeleteAll(false);
CurrPage.Update(false);
end;
var
BaseCurrencyCode: Code[10];
PbtTaskId: Integer;
}

Просмотреть файл

@ -0,0 +1,90 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
pageextension 50100 "O365 Activities Ext Currency" extends "O365 Activities"
{
layout
{
addafter("Overdue Purch. Invoice Amount")
{
field("EUR/DKK"; EURDKK)
{
Visible = true;
ApplicationArea = Basic, Suite;
DrillDownPageID = "Currencies";
Caption = 'EUR/DKK - Latest';
ToolTip = 'Latest EUR/DKK rate from the market';
trigger OnDrillDown()
begin
DrillDownToCurrency('EUR');
end;
}
field("EUR/USD"; EURUSD)
{
Visible = true;
ApplicationArea = Basic, Suite;
DrillDownPageID = "Currencies";
Caption = 'EUR/USD - Latest';
ToolTip = 'Latest EUR/USD rate from the market';
trigger OnDrillDown()
begin
DrillDownToCurrency('EUR');
end;
}
}
}
trigger OnAfterGetCurrRecord()
var
PbtParameters: Dictionary of [Text, Text];
DemoAlCurrencySetup: Record "Demo AL Currency Setup";
begin
if (PbtTaskId = 0) then begin
PbtParameters.Set('Date', 'latest');
PbtParameters.Set('CurrencyBase', 'EUR');
PbtParameters.Set('Currencies', 'USD,DKK');
DemoAlCurrencySetup.Get('SETUP');
PbtParameters.Set('SleepSimulation', Format(DemoAlCurrencySetup.SleepDurationRoleCenter)); // Delay to simulate a slow HTTP call
// Testability
OnBeforePageBackgroundTaskSchedule(PbtParameters);
// Default timeout is 2min, max is 10min.
CurrPage.EnqueueBackgroundTask(PbtTaskId, Codeunit::CurrencyRetriever, PbtParameters, 100000, PageBackgroundTaskErrorLevel::Warning);
end;
end;
trigger OnPageBackgroundTaskCompleted(TaskId: Integer; Results: Dictionary of [Text, Text])
begin
if (TaskId = PbtTaskId) then begin
Evaluate(EURDKK, Results.Get('DKK'));
Evaluate(EURUSD, Results.Get('USD'));
end;
end;
local procedure DrillDownToCurrency(Code: Code[10])
var
Currency: Record Currency;
begin
Currency.Get(Code);
Page.Run(Page::Currencies, Currency);
end;
// For testability of the page.
[IntegrationEvent(false, false)]
local procedure OnBeforePageBackgroundTaskSchedule(var PbtParameters: Dictionary of [Text, Text])
begin
end;
var
EURDKK: Decimal;
EURUSD: Decimal;
EURSEK: Decimal;
PbtTaskId: Integer;
MockHttpResponse: Text;
}

Просмотреть файл

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<PermissionSets>
<PermissionSet RoleID="DGI.DEMO.AL.CURRENCY" RoleName="dgi.demo.al.currency">
<Permission>
<ObjectType>5</ObjectType>
<ObjectID>50100</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>5</ObjectType>
<ObjectID>50101</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>5</ObjectType>
<ObjectID>50130</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>5</ObjectType>
<ObjectID>50131</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>0</ObjectType>
<ObjectID>50101</ObjectID>
<ReadPermission>1</ReadPermission>
<InsertPermission>1</InsertPermission>
<ModifyPermission>1</ModifyPermission>
<DeletePermission>1</DeletePermission>
<ExecutePermission>0</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>1</ObjectType>
<ObjectID>50101</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>0</ObjectType>
<ObjectID>50100</ObjectID>
<ReadPermission>1</ReadPermission>
<InsertPermission>1</InsertPermission>
<ModifyPermission>1</ModifyPermission>
<DeletePermission>1</DeletePermission>
<ExecutePermission>0</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>1</ObjectType>
<ObjectID>50100</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>8</ObjectType>
<ObjectID>50100</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>8</ObjectType>
<ObjectID>50103</ObjectID>
<ReadPermission>0</ReadPermission>
<InsertPermission>0</InsertPermission>
<ModifyPermission>0</ModifyPermission>
<DeletePermission>0</DeletePermission>
<ExecutePermission>1</ExecutePermission>
<SecurityFilter />
</Permission>
</PermissionSet>
</PermissionSets>

Просмотреть файл

@ -0,0 +1,36 @@
# Purpose
This AL extension is showing how to take advantage of Page Background Tasks in Business Central.
# Page Background Task
A page background task can run a codeunit (without a UI) in a read-only child session of the page session. On completion of the task, a completion trigger with the result is invoked on the page session.
If the page is closed before the task completes or the page record ID changed, the task is canceled.
# Requirement
Business Central Fall Release 2019 (Also known as version 15)
# How to run
Clone the repository, build the extension and publish it to your sandbox.
**On the Role Center 9022 (Business Manager Role Center), two cues will appears:**
* EUR/DKK - Latest
* EUR/USD - Latest
**In the "Currencies" list page, a factbox will appear:**
* "Latest currency rates"
**A setup page:**
* "Demo AL Currency Setup" : To setup the delays for demo purposes
# Design
The code unit "CurrencyRetriever" is running in a child session of the parent session.
The parent session continues to run, not affected by the child session (if it blocks for example, performing an extensive operation/waiting for IO).
The important concept to understand is the "link" between the page current record, and the page background task.
**If CurrPage.Rec primary key changes, the PBT is automatically cancelled.**
In "O365 Activities Ext Currency", the PBT is enqueued on OnAfterGetCurrRecord(), and therefor is linked to the current page record. ("Activities Cue" record)
In "Currencies PageExt", the PBT is enqueued on OnAfterGetCurrRecord(), and therefor is linked to the currently selected record of the list. Changing the record in the list will cancel the page background task automatically.

Просмотреть файл

@ -0,0 +1,54 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
codeunit 50101 CurrencySetup
{
Subtype = Install;
trigger OnInstallAppPerCompany()
var
Currency: Record Currency;
DemoAlCurrencySetup: Record "Demo AL Currency Setup";
ExchangeRate: Record "Currency Exchange Rate";
begin
if not Currency.Get('DKK') then begin
Currency.InitRoundingPrecision();
Currency.Code := 'DKK';
Currency.Description := 'Danish Kroner';
Currency."ISO Code" := 'DKK';
Currency."ISO Numeric Code" := '208';
Currency.Symbol := 'kr';
Currency.Insert(true);
end;
if not Currency.Get('SEK') then begin
Currency.InitRoundingPrecision();
Currency.Code := 'SEK';
Currency.Description := 'Swedish Krona';
Currency."ISO Code" := 'SEK';
Currency."ISO Numeric Code" := '752';
Currency.Symbol := 'kr';
Currency.Insert(true);
end;
if not Currency.Get('USD') then begin
Currency.InitRoundingPrecision();
Currency.Code := 'USD';
Currency.Description := 'United States Dollar';
Currency."ISO Code" := 'USD';
Currency."ISO Numeric Code" := '840';
Currency.Symbol := '$';
Currency.Insert(true);
end;
if not DemoAlCurrencySetup.Get('SETUP') then begin
DemoAlCurrencySetup.Init();
DemoAlCurrencySetup."Primary Key" := 'SETUP';
DemoAlCurrencySetup.SleepDurationFactbox := 2000;
DemoAlCurrencySetup.SleepDurationRoleCenter := 2000;
DemoAlCurrencySetup.Insert();
end;
end;
}

Просмотреть файл

@ -0,0 +1,48 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
codeunit 50130 CurrencyRetrieverTest
{
Subtype = Test;
TestPermissions = Disabled;
[Test]
procedure TestOnRun()
var
// To run the background code unit, any page can be used.
PbtTestPage: TestPage "O365 Address";
PbtParameters: Dictionary of [Text, Text];
PbtResults: Dictionary of [Text, Text];
Err: Label 'Expected \%1, Actual: \%2';
begin
// Arrange
// TestPage.RunPageBackgroundTask requires the page to be opened today (Bug reported to MSFT)
PbtTestPage.OpenView();
PbtParameters.Set('Date', '2019-09-04');
PbtParameters.Set('CurrencyBase', 'EUR');
PbtParameters.Set('Currencies', 'DKK,SEK,USD');
PbtParameters.Set('MockHttpResponse', '{"rates":{"USD":1.0938,"SEK":10.6598,"DKK":7.4643},"base":"EUR","date":"2019-09-26"}');
// Act
PbtResults := pbtTestPage.RunPageBackgroundTask(Codeunit::CurrencyRetriever, PbtParameters);
// Assert
ArePbtResultEqual(1.0938, pbtResults.Get('USD'), 'USD');
ArePbtResultEqual(10.6598, pbtResults.Get('SEK'), 'SEK');
ArePbtResultEqual(7.4643, pbtResults.Get('DKK'), 'DKK');
end;
local procedure ArePbtResultEqual(Expected: Decimal; Actual: Text; Msg: Text)
var
actualValue: Decimal;
AreEqualFailedMsg: Label 'Assert.AreEqual failed. Expected:<%1>. Actual:<%2>. %3.', Locked = true;
begin
Evaluate(actualValue, Actual);
if Expected <> actualValue then
Error(AreEqualFailedMsg, Expected, Actual, Msg)
end;
}

Просмотреть файл

@ -0,0 +1,41 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
codeunit 50131 O365ActivitiesExtensionTest
{
Subtype = Test;
TestPermissions = Disabled;
EventSubscriberInstance = Manual;
[Test]
procedure TestPageExtension()
var
ActivitesPage: TestPage "O365 Activities";
Err: Label 'Expected \%1, Actual: \%2';
begin
// Arrange
BindSubscription(O365ActivitiesExtensionTest);
// Act
ActivitesPage.OpenView();
// Assert
ActivitesPage."EUR/DKK".AssertEquals(7.4643);
ActivitesPage."EUR/USD".AssertEquals(1.0937);
end;
[EventSubscriber(ObjectType::Page, Page::"O365 Activities", 'OnBeforePageBackgroundTaskSchedule', '', false, false)]
local procedure OnBeforePageBackgroundTaskSchedule(var PbtParameters: Dictionary of [Text, Text])
begin
// Arrange: Mock the HTTP response
PbtParameters.Add('MockHttpResponse', '{"rates":{"USD":1.0937,"SEK":10.6598,"DKK":7.4643},"base":"EUR","date":"2019-09-26"}');
// Arrange: Remove the simulation sleep
PbtParameters.Remove('SleepSimulation');
end;
var
O365ActivitiesExtensionTest: Codeunit O365ActivitiesExtensionTest;
}

Просмотреть файл

@ -0,0 +1,37 @@
{
"id": "36ac31a9-2f65-48eb-a141-031f2f075a8e",
"name": "Sample.PageBackgroundtask.Currency",
"publisher": "Microsoft",
"version": "1.0.0.0",
"brief": "Sample project for Page Background Tasks",
"description": "Sample project for Page Background Tasks",
"privacyStatement": "",
"EULA": "",
"help": "",
"url": "",
"logo": "",
"dependencies": [
{
"appId": "63ca2fa4-4f03-4f2b-a480-172fef340d3f",
"publisher": "Microsoft",
"name": "System Application",
"version": "1.0.0.0"
},
{
"appId": "437dbf0e-84ff-417a-965d-ed2bb9650972",
"publisher": "Microsoft",
"name": "Base Application",
"version": "15.0.0.0"
}
],
"screenshots": [],
"platform": "15.0.0.0",
"idRanges": [
{
"from": 50100,
"to": 50149
}
],
"showMyCode": true,
"runtime": "4.0"
}