Codeunit 2717 Page Summary Provider Impl., source in 25
Source242526272829metadata in 25
src/System Application/App/Page Summary Provider/src/PageSummaryProviderImpl.Codeunit.al459 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace System.Integration;
using System;
using System.Environment;
using System.Reflection;
using System.Text;
/// <summary>
/// Implements functionality to get summary data for a given object.
/// </summary>
codeunit 2717 "Page Summary Provider Impl."
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;
Permissions = tabledata Company = r,
tabledata "Page Metadata" = r,
tabledata "Tenant Media Set" = r,
tabledata "Tenant Media Thumbnails" = r;
procedure GetPageSummary(Parameters: Text): Text
var
PageSummaryParameters: Record "Page Summary Parameters";
begin
PageSummaryParameters.FromJson(Parameters);
exit(GetPageSummary(PageSummaryParameters));
end;
procedure GetPageSummary(PageSummaryParameters: Record "Page Summary Parameters"): Text
begin
if PageSummaryParameters.Bookmark <> '' then
exit(GetPageSummary(PageSummaryParameters."Page ID", PageSummaryParameters.Bookmark, PageSummaryParameters."Include Binary Data"));
if not IsNullGuid(PageSummaryParameters."Record SystemID") then
exit(GetPageSummary(PageSummaryParameters."Page ID", PageSummaryParameters."Record SystemID", PageSummaryParameters."Include Binary Data"));
exit(GetPageSummary(PageSummaryParameters."Page ID", '', PageSummaryParameters."Include Binary Data"));
end;
procedure GetPageSummary(PageId: Integer; Bookmark: Text; IncludeBinaryData: Boolean): Text
var
RecId: RecordId;
ResultJsonObject: JsonObject;
begin
// Add header
AddPageSummaryHeader(PageId, ResultJsonObject);
// If show summary record is false, then exit with summary type caption
if not PageSummarySettings.IsShowRecordSummaryEnabled() then
exit(Format(ResultJsonObject));
if Bookmark = '' then
exit(Format(ResultJsonObject)); // There is no bookmark, so just return page header
// Initialize variables
if not Evaluate(RecId, Bookmark, 10) then begin // 10 = Evaluate string into RecordId
AddErrorMessage(ResultJsonObject, InvalidBookmarkErrorCodeTok, InvalidBookmarkErrorMessageTxt);
exit(Format(ResultJsonObject)); // Bookmark is invalid, so returning the information we actually have about the page
end;
// Add summary fields and record fields
AddFields(PageId, RecId, Bookmark, ResultJsonObject, IncludeBinaryData);
exit(Format(ResultJsonObject));
end;
procedure GetPageSummary(PageId: Integer; SystemId: Guid; IncludeBinaryData: Boolean): Text
var
PageMetadata: Record "Page Metadata";
RecId: RecordId;
SourceRecordRef: RecordRef;
ResultJsonObject: JsonObject;
Bookmark: Text;
begin
// Add header
AddPageSummaryHeader(PageId, ResultJsonObject);
// Initialize variables
if not PageMetadata.Get(PageId) then
exit(Format(ResultJsonObject));
SourceRecordRef.Open(PageMetadata.SourceTable);
if not SourceRecordRef.GetBySystemId(SystemId) then begin
AddErrorMessage(ResultJsonObject, InvalidSystemIdErrorCodeTok, InvalidSystemIdErrorMessageTxt);
exit(Format(ResultJsonObject)); // System ID is invalid, so returning the information we actually have about the page
end;
AddUrl(ResultJsonObject, PageId, SourceRecordRef);
// If show summary record is false, then exit with summary type caption
if not PageSummarySettings.IsShowRecordSummaryEnabled() then
exit(Format(ResultJsonObject));
RecId := SourceRecordRef.RecordId;
Bookmark := Format(RecId, 0, 10); // 10 = Format RecordId into string
if Bookmark = '' then
exit(Format(ResultJsonObject));
// Add summary fields and record fields
AddFields(PageId, RecId, Bookmark, ResultJsonObject, IncludeBinaryData);
exit(Format(ResultJsonObject));
end;
procedure GetPageUrlBySystemID(PageId: Integer; SystemId: Guid): Text
var
PageMetadata: Record "Page Metadata";
SourceRecordRef: RecordRef;
ResultJsonObject: JsonObject;
begin
ResultJsonObject.Add('version', GetVersion());
if not PageMetadata.Get(PageId) then begin
AddErrorMessage(ResultJsonObject, PageNotFoundErrorCodeTok, StrSubstNo(PageNotFoundErrorMessageTxt, PageId));
exit(Format(ResultJsonObject));
end;
SourceRecordRef.Open(PageMetadata.SourceTable);
if not SourceRecordRef.GetBySystemId(SystemId) then begin
AddErrorMessage(ResultJsonObject, InvalidSystemIdErrorCodeTok, InvalidSystemIdErrorMessageTxt);
exit(Format(ResultJsonObject));
end;
AddUrl(ResultJsonObject, PageId, SourceRecordRef);
Session.LogMessage('0000JAT', StrSubstNo(GetPageUrlSuccessTelemetryTxt, PageId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
exit(Format(ResultJsonObject));
end;
procedure GetVersion(): Text[30]
begin
exit('1.1');
end;
procedure InitializePageSummarySettingsFromJson(JsonText: Text; var PageSummaryParameters: Record "Page Summary Parameters")
var
PageSummaryJsonObject: JsonObject;
ParsedJsonToken: JsonToken;
begin
Clear(PageSummarySettings);
PageSummaryJsonObject.ReadFrom(JsonText);
if PageSummaryJsonObject.Get(PageIDTok, ParsedJsonToken) then
PageSummaryParameters."Page ID" := ParsedJsonToken.AsValue().AsInteger();
if PageSummaryJsonObject.Get(BookmarkTok, ParsedJsonToken) then
#pragma warning disable AA0139
PageSummaryParameters.Bookmark := ParsedJsonToken.AsValue().AsText()
#pragma warning restore AA0139
else
if PageSummaryJsonObject.Get(RecordSystemIdTok, ParsedJsonToken) then
PageSummaryParameters."Record SystemID" := ParsedJsonToken.AsValue().AsText();
if PageSummaryJsonObject.Get(IncludeBinaryDataTok, ParsedJsonToken) then
PageSummaryParameters."Include Binary Data" := ParsedJsonToken.AsValue().AsBoolean();
end;
local procedure AddFields(PageId: Integer; RecId: RecordId; Bookmark: Text; var ResultJsonObject: JsonObject; IncludeBinaryData: Boolean)
begin
// Add Summary fields
// Fields summary is a "summary" of the fields (i.e. it could be the brick definition, or some custom AL could provide its own summary).
GetFieldsSummary(PageId, RecId, Bookmark, ResultJsonObject, IncludeBinaryData);
// Add Record fields
// Record fields are the backing record fields that are visible on the page.
if not ResultJsonObject.Contains('error') then
GetRecordFields(PageId, Bookmark, ResultJsonObject);
end;
local procedure GetFieldsSummary(PageId: Integer; RecId: RecordId; Bookmark: Text; var ResultJsonObject: JsonObject; IncludeBinaryData: Boolean)
var
PageSummaryProvider: Codeunit "Page Summary Provider";
FieldsJsonArray: JsonArray;
Handled: Boolean;
begin
// Allow partner to override returned fields
PageSummaryProvider.OnBeforeGetPageSummary(PageId, RecId, FieldsJsonArray, Handled);
if Handled then begin // Partner overrode fields
Session.LogMessage('0000D73', StrSubstNo(OnBeforeGetPageSummaryWasHandledTxt, PageId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
AddFieldsJsonArrayToResult(FieldsJsonArray, ResultJsonObject);
exit;
end;
// Get summary fields
if not TryGetPageSummaryFields(PageId, RecId, Bookmark, ResultJsonObject, IncludeBinaryData) then
AddErrorMessage(ResultJsonObject, FailedGetSummaryFieldsCodeTok, GetLastErrorText());
end;
local procedure GetRecordFields(PageId: Integer; Bookmark: Text; var ResultJsonObject: JsonObject)
begin
// Get all visible and available table fields that back the controls that are visible on the page
if not TryGetAvailableRecordFieldsData(PageId, Bookmark, ResultJsonObject) then
Session.LogMessage('0000NFZ', StrSubstNo(GetRecordFieldsFailureTelemetryTxt, PageId), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
exit;
end;
[TryFunction]
local procedure TryGetAvailableRecordFieldsData(PageId: Integer; Bookmark: Text; var ResultJsonObject: JsonObject)
var
GenericList: DotNet GenericList1;
NavPageSummaryALFunctions: DotNet NavPageSummaryALFunctions;
NavPageSummaryALResponse: DotNet NavPageSummaryALResponse;
NavPageSummaryALField: DotNet NavPageSummaryALField;
RecordFieldsJsonArray: JsonArray;
begin
GenericList := NavPageSummaryALFunctions.GetAvailableTableFields(PageId);
if IsNull(GenericList) then begin
Session.LogMessage('0000NCO', StrSubstNo(NoRecordFieldsFoundTelemetryTxt, PageId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
exit;
end;
if (GenericList.Count() > 0) then begin
NavPageSummaryALResponse := NavPageSummaryALFunctions.GetSummary(PageId, Bookmark, GenericList);
if not NavPageSummaryALResponse.Success then begin
Session.LogMessage('0000NCX', StrSubstNo(SummaryFailureTelemetryTxt, PageId), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
exit;
end;
// Get field values
foreach NavPageSummaryALField in NavPageSummaryALResponse.SummaryFields do
AddFieldToFieldsJsonArray(NavPageSummaryALField, RecordFieldsJsonArray, true, false);
end;
ResultJsonObject.Add('recordFields', RecordFieldsJsonArray);
end;
local procedure AddErrorMessage(var ResultJsonObject: JsonObject; ErrorCode: Text; ErrorMessage: Text)
var
ErrorJsonObject: JsonObject;
begin
Session.LogMessage('0000EAX', ErrorCode, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
ErrorJsonObject.Add('code', ErrorCode);
ErrorJsonObject.Add('message', ErrorMessage);
ResultJsonObject.Add('error', ErrorJsonObject);
end;
local procedure AddUrl(var ResultJsonObject: JsonObject; PageId: Integer; SourceRecordRef: RecordRef)
var
Url: Text;
begin
Url := GetUrl(ClientType::Web, CompanyName(), ObjectType::Page, PageId, SourceRecordRef);
ResultJsonObject.Add('url', Url);
end;
local procedure AddPageSummaryHeader(PageId: Integer; var ResultJsonObject: JsonObject)
var
PageMetadata: Record "Page Metadata";
PageCaption: Text;
begin
if PageMetadata.Get(PageId) then
PageCaption := PageMetadata.Caption
else
PageCaption := StrSubstNo(PageTxt, PageId);
ResultJsonObject.Add('version', GetVersion());
ResultJsonObject.Add('pageCaption', PageCaption);
ResultJsonObject.Add('pageType', format(PageMetadata.PageType));
ResultJsonObject.Add('summaryType', GetSummaryName(Enum::"Summary Type"::Caption)); // default summary type is caption
ResultJsonObject.Add('cardPageId', PageMetadata.CardPageID);
end;
[TryFunction]
local procedure TryGetPageSummaryFields(PageId: Integer; RecId: RecordId; Bookmark: Text; var ResultJsonObject: JsonObject; IncludeBinaryData: Boolean)
var
PageSummaryProvider: Codeunit "Page Summary Provider";
NavPageSummaryALFunctions: DotNet NavPageSummaryALFunctions;
GenericList: DotNet GenericList1;
NavPageSummaryALResponse: DotNet NavPageSummaryALResponse;
NavPageSummaryALField: DotNet NavPageSummaryALField;
FieldsJsonArray: JsonArray;
PageSummaryFieldList: List of [Integer];
PageSummaryField: Integer;
ErrorMessage: Text;
begin
GenericList := NavPageSummaryALFunctions.GetSummaryFields(PageId);
if IsNull(GenericList) then
exit;
foreach PageSummaryField in GenericList do
PageSummaryFieldList.Add(PageSummaryField);
if not IncludeBinaryData then
RemoveMediaAndBlobFields(RecId, PageSummaryFieldList);
CorrectFieldOrderingOfBrick(PageSummaryFieldList);
// Allow partners to override fields to be shown + order
PageSummaryProvider.OnAfterGetSummaryFields(PageId, RecId, PageSummaryFieldList);
GenericList.Clear();
foreach PageSummaryField in PageSummaryFieldList do
GenericList.Add(PageSummaryField);
if (GenericList.Count() > 0) then begin
NavPageSummaryALResponse := NavPageSummaryALFunctions.GetSummary(PageId, Bookmark, GenericList);
if not NavPageSummaryALResponse.Success then begin
Session.LogMessage('0000DGV', StrSubstNo(SummaryFailureTelemetryTxt, PageId), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PageSummaryCategoryLbl);
ErrorMessage := NavPageSummaryALResponse.ErrorMessage;
Error(ErrorMessage);
end;
// Get field values
foreach NavPageSummaryALField in NavPageSummaryALResponse.SummaryFields do
AddFieldToFieldsJsonArray(NavPageSummaryALField, FieldsJsonArray, false, true);
end;
// Allow partner to finally override field names and values
PageSummaryProvider.OnAfterGetPageSummary(PageId, RecId, FieldsJsonArray);
AddFieldsJsonArrayToResult(FieldsJsonArray, ResultJsonObject);
end;
local procedure RemoveMediaAndBlobFields(RecId: RecordId; var PageSummaryFieldList: List of [Integer])
var
RecordField: Record Field;
Index: Integer;
PageSummaryField: Integer;
begin
if PageSummaryFieldList.Count() = 0 then
exit;
Index := 1;
repeat
PageSummaryField := PageSummaryFieldList.Get(Index);
RecordField.Get(RecId.TableNo, PageSummaryField);
if (RecordField.Type in [RecordField.Type::Media, RecordField.Type::MediaSet, RecordField.Type::Blob]) then
PageSummaryFieldList.RemoveAt(Index)
else
Index += 1;
until Index > PageSummaryFieldList.Count();
end;
local procedure GetSummaryName(SummaryType: Enum "Summary Type"): Text;
var
Index: Integer;
begin
Index := SummaryType.Ordinals.IndexOf(SummaryType.AsInteger());
exit(SummaryType.Names().Get(Index));
end;
local procedure AddFieldsJsonArrayToResult(var FieldsJsonArray: JsonArray; var ResultJsonObject: JsonObject)
begin
if FieldsJsonArray.Count() > 0 then
ResultJsonObject.Replace('summaryType', GetSummaryName(Enum::"Summary Type"::Brick));
ResultJsonObject.Add('fields', FieldsJsonArray);
end;
local procedure CorrectFieldOrderingOfBrick(var PageSummaryFieldList: List of [Integer])
var
TempValue: Integer;
begin
// Currently we just want to swap first and second field if there is more than one
if PageSummaryFieldList.Count() <= 1 then
exit;
TempValue := PageSummaryFieldList.Get(1);
PageSummaryFieldList.RemoveAt(1);
PageSummaryFieldList.Insert(2, TempValue);
end;
local procedure AddFieldToFieldsJsonArray(NavPageSummaryALField: DotNet NavPageSummaryALField; var FieldsJsonArray: JsonArray; OnlyIncludeNonEmptyFieldValues: Boolean; IncludePictures: Boolean)
var
FieldsJsonObject: JsonObject;
FieldValue: Text;
FieldType: Text;
MimeType: Text;
begin
if NavPageSummaryALField.FieldType = 33794 then // 33794 == Blob
exit;
FieldValue := NavPageSummaryALField.Value.ToString();
FieldType := NavPageSummaryALField.FieldType.ToString();
if OnlyIncludeNonEmptyFieldValues then
if FieldValue = '' then
exit;
// Handle pictures - 26209 == MediaSet, 26208 == Media
if NavPageSummaryALField.FieldType = 26208 then begin
if not IncludePictures then
exit;
ExtractPictureFromMedia(NavPageSummaryALField.Value.ToString(), FieldValue, MimeType, FieldType);
end;
if NavPageSummaryALField.FieldType = 26209 then begin
if not IncludePictures then
exit;
ExtractPictureFromMediaSet(NavPageSummaryALField.Value.ToString(), FieldValue, MimeType, FieldType);
end;
// Add the actual field
FieldsJsonObject.Add('caption', NavPageSummaryALField.Caption);
if NavPageSummaryALField.ExtendedType <> NavPageSummaryALField.ExtendedType::Undefined then
FieldsJsonObject.Add('extendedType', NavPageSummaryALField.ExtendedType.ToString());
FieldsJsonObject.Add('fieldValue', FieldValue);
FieldsJsonObject.Add('fieldType', FieldType);
FieldsJsonObject.Add('tooltip', NavPageSummaryALField.Tooltip);
if MimeType <> '' then
FieldsJsonObject.Add('mimeType', MimeType);
FieldsJsonArray.Add(FieldsJsonObject);
end;
local procedure ExtractPictureFromMedia(ImageGuid: Guid; var FieldValue: Text; var MimeType: Text; var FieldType: Text)
var
TenantMediaThumbnails: Record "Tenant Media Thumbnails";
Base64Convert: Codeunit "Base64 Convert";
InStr: InStream;
begin
FieldType := 'Media';
// Filter on large image thumbnail
// Whenever an image is stored, we also store a large image thumbnail in dimensions 240 x 240 (used in grid view)
TenantMediaThumbnails.SetRange("Media Id", ImageGuid);
TenantMediaThumbnails.SetRange("Height", 240);
TenantMediaThumbnails.SetRange("Width", 240);
if not TenantMediaThumbnails.FindFirst() then begin
Clear(FieldValue);
Clear(MimeType);
exit;
end;
TenantMediaThumbnails.CalcFields(Content);
TenantMediaThumbnails.Content.CreateInStream(InStr);
FieldValue := Base64Convert.ToBase64(InStr);
MimeType := TenantMediaThumbnails."Mime Type";
end;
local procedure ExtractPictureFromMediaSet(ImageGuid: Guid; var FieldValue: Text; var MimeType: Text; var FieldType: Text)
var
TenantMediaSet: Record "Tenant Media Set";
begin
TenantMediaSet.SetRange(Id, ImageGuid);
if TenantMediaSet.FindFirst() then;
ExtractPictureFromMedia(TenantMediaSet."Media ID".MediaId, FieldValue, MimeType, FieldType);
end;
var
PageSummarySettings: Codeunit "Page Summary Settings";
PageTxt: Label 'Page %1', Comment = '%1 is a whole number, ex. 10';
PageSummaryCategoryLbl: Label 'Page Summary Provider', Locked = true;
OnBeforeGetPageSummaryWasHandledTxt: Label 'OnBeforeGetPageSummary event was handled for page %1.', Locked = true;
SummaryFailureTelemetryTxt: Label 'Failure to get summary for page %1.', Locked = true;
InvalidBookmarkErrorCodeTok: Label 'InvalidBookmark', Locked = true;
InvalidBookmarkErrorMessageTxt: Label 'The bookmark is invalid.';
FailedGetSummaryFieldsCodeTok: Label 'FailedGettingPageSummaryFields', Locked = true;
InvalidSystemIdErrorCodeTok: Label 'InvalidSystemId', Locked = true;
InvalidSystemIdErrorMessageTxt: Label 'The system ID is invalid.';
PageNotFoundErrorCodeTok: Label 'PageNotFound', Locked = true;
PageNotFoundErrorMessageTxt: Label 'Page %1 is not found.', Comment = '%1 is a whole number, ex. 10';
GetPageUrlSuccessTelemetryTxt: Label 'Successfully added url for page %1.', Locked = true;
NoRecordFieldsFoundTelemetryTxt: Label 'No record fields found for page %1.', Locked = true;
GetRecordFieldsFailureTelemetryTxt: Label 'Failure to get record fields for page %1.', Locked = true;
PageIDTok: Label 'pageId', Locked = true;
RecordSystemIdTok: Label 'recordSystemId', Locked = true;
BookmarkTok: Label 'bookmark', Locked = true;
IncludeBinaryDataTok: Label 'includeBinaryData', Locked = true;
}