8000 [DRAFT] Pagination for microsoft graph module by Drakonian · Pull Request #3940 · microsoft/BCApps · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[DRAFT] Pagination for microsoft graph module #3940

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/System Application/App/MicrosoftGraph/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"idRanges": [
{
"from": 9350,
"to": 9359
"to": 9361
}
],
"target": "OnPrem",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,50 @@ codeunit 9350 "Graph Client"
begin
exit(GraphClientImpl.Delete(RelativeUriToResource, GraphOptionalParameters, HttpResponseMessage));
end;

#region Pagination Support

/// <summary>
/// Get any request to the microsoft graph API with pagination support
/// </summary>
/// <remarks>Does not require UI interaction. This method handles pagination automatically.</remarks>
/// <param name="RelativeUriToResource">A relativ uri including the resource segments</param>
/// <param name="GraphOptionalParameters">A wrapper for optional header and query parameters</param>
/// <param name="GraphPaginationData">The pagination data object to track pagination state</param>
/// <param name="HttpResponseMessage">The response message object.</param>
/// <returns>True if the operation was successful; otherwise - false.</returns>
/// <error>Authentication failed.</error>
procedure GetWithPagination(RelativeUriToResource: Text; GraphOptionalParameters: Codeunit "Graph Optional Parameters"; var GraphPaginationData: Codeunit "Graph Pagination Data"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean
begin
exit(GraphClientImpl.GetWithPagination(RelativeUriToResource, GraphOptionalParameters, GraphPaginationData, HttpResponseMessage));
end;

/// <summary>
/// Get the next page of results using pagination data
/// </summary>
/// <remarks>Does not require UI interaction.</remarks>
/// <param name="GraphPaginationData">The pagination data object containing the next link</param>
/// <param name="HttpResponseMessage">The response message object.</param>
/// <returns>True if the operation was successful; otherwise - false.</returns>
/// <error>Authentication failed.</error>
procedure GetNextPage(var GraphPaginationData: Codeunit "Graph Pagination Data"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean
begin
exit(GraphClientImpl.GetNextPage(GraphPaginationData, HttpResponseMessage));
end;

/// <summary>
/// Get all pages of results automatically
/// </summary>
/// <remarks>Does not require UI interaction. This method fetches all pages automatically and returns the combined results.</remarks>
/// <param name="RelativeUriToResource">A relativ uri including the resource segments</param>
/// <param name="GraphOptionalParameters">A wrapper for optional header and query parameters</param>
/// <param name="JsonResults">A JSON array containing all results from all pages</param>
/// <returns>True if the operation was successful; otherwise - false.</returns>
/// <error>Authentication failed.</error>
procedure GetAllPages(RelativeUriToResource: Text; GraphOptionalParameters: Codeunit "Graph Optional Parameters"; var JsonResults: JsonArray): Boolean
begin
exit(GraphClientImpl.GetAllPages(RelativeUriToResource, GraphOptionalParameters, JsonResults));
end;

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,64 @@ codeunit 9351 "Graph Client Impl."
exit(HttpResponseMessage.GetIsSuccessStatusCode());
end;

procedure GetWithPagination(RelativeUriToResource: Text; GraphOptionalParameters: Codeunit "Graph Optional Parameters"; var GraphPaginationData: Codeunit "Graph Pagination Data"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean
var
GraphPaginationHelper: Codeunit "Graph Pagination Helper";
begin
// Apply page size if set
GraphPaginationHelper.ApplyPageSize(GraphOptionalParameters, GraphPaginationData);

// Make the request
if not Get(RelativeUriToResource, GraphOptionalParameters, HttpResponseMessage) then
exit(false);

// Extract pagination data
GraphPaginationHelper.ExtractNextLink(HttpResponseMessage, GraphPaginationData);
exit(HttpResponseMessage.GetIsSuccessStatusCode());
end;

procedure GetNextPage(var GraphPaginationData: Codeunit "Graph Pagination Data"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean
var
GraphPaginationHelper: Codeunit "Graph Pagination Helper";
NextLink: Text;
begin
NextLink := GraphPaginationData.GetNextLink();

if NextLink = '' then
exit(false);

GraphRequestHelper.SetRestClient(RestClient);
HttpResponseMessage := GraphRequestHelper.GetByFullUrl(NextLink);

// Update pagination data
GraphPaginationHelper.ExtractNextLink(HttpResponseMessage, GraphPaginationData);
exit(HttpResponseMessage.GetIsSuccessStatusCode());
end;

procedure GetAllPages(RelativeUriToResource: Text; GraphOptionalParameters: Codeunit "Graph Optional Parameters"; var JsonResults: JsonArray): Boolean
var
GraphPaginationHelper: Codeunit "Graph Pagination Helper";
GraphPaginationData: Codeunit "Graph Pagination Data";
HttpResponseMessage: Codeunit "Http Response Message";
IterationCount: Integer;
begin
// First request with pagination
if not GetWithPagination(RelativeUriToResource, GraphOptionalParameters, GraphPaginationData, HttpResponseMessage) then
exit(false);

// Process first page
GraphPaginationHelper.CombineValueArrays(HttpResponseMessage, JsonResults);

// Fetch remaining pages
while GraphPaginationData.HasMorePages() and GraphPaginationHelper.IsWithinIterationLimit(IterationCount, GraphPaginationHelper.GetMaxIterations()) do begin
if not GetNextPage(GraphPaginationData, HttpResponseMessage) then
exit(false);

GraphPaginationHelper.CombineValueArrays(HttpResponseMessage, JsonResults);
end;

exit(true);
end;

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// ------------------------------------------------------------------------------------------------
// 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.Graph;

/// <summary>
/// Holder for pagination data when working with Microsoft Graph API responses.
/// </summary>
codeunit 9360 "Graph Pagination Data"
{
Access = Public;
InherentEntitlements = X;
InherentPermissions = X;

var
GraphPaginationDataImpl: Codeunit "Graph Pagination Data Impl.";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe be able to specify the value $skip for skip here, too?

Following scenario:
Someone wants to call the GetWithPagination but it should return only all results after the first record.

Technically I think that we don't need this, since we can specify the value for $skip in the optional parameters.

Maybe we should provide a Readme with some examples how to use those new procedures instead?

/// <summary>
/// Sets the next link URL for retrieving the next page of results.
/// </summary>
/// <param name="NewNextLink">The @odata.nextLink value from the Graph response.</param>
procedure SetNextLink(NewNextLink: Text)
begin
GraphPaginationDataImpl.SetNextLink(NewNextLink);
end;

/// <summary>
/// Gets the current next link URL.
/// </summary>
/// <returns>The URL to retrieve the next page of results.</returns>
procedure GetNextLink(): Text
begin
exit(GraphPaginationDataImpl.GetNextLink());
end;

/// <summary>
/// Checks if there are more pages available.
/// </summary>
/// <returns>True if more pages are available; otherwise false.</returns>
procedure HasMorePages(): Boolean
begin
exit(GraphPaginationDataImpl.HasMorePages());
end;

/// <summary>
/// Sets the page size for pagination requests.
/// </summary>
/// <param name="NewPageSize">The number of items to retrieve per page (max 999).</param>
procedure SetPageSize(NewPageSize: Integer)
begin
GraphPaginationDataImpl.SetPageSize(NewPageSize);
end;

/// <summary>
/// Gets the current page size.
/// </summary>
/// <returns>The number of items per page.</returns>
procedure GetPageSize(): Integer
begin
exit(GraphPaginationDataImpl.GetPageSize());
end;

/// <summary>
/// Gets the default page size.
/// </summary>
/// <returns>The default number of items per page.</returns>
procedure GetDefaultPageSize(): Integer
begin
exit(GraphPaginationDataImpl.GetDefaultPageSize());
end;

/// <summary>
/// Resets the pagination data to initial state.
/// </summary>
procedure Reset()
begin
GraphPaginationDataImpl.Reset();
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// ------------------------------------------------------------------------------------------------
// 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.Graph;

codeunit 9361 "Graph Pagination Data Impl."
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
NextLink: Text;
PageSize: Integer;
DefaultPageSizeErr: Label 'Page size must be between 1 and 999.';

procedure SetNextLink(NewNextLink: Text)
begin
NextLink := NewNextLink;
end;

procedure GetNextLink(): Text
begin
exit(NextLink);
end;

procedure HasMorePages(): Boolean
begin
exit(NextLink <> '');
end;

procedure SetPageSize(NewPageSize: Integer)
begin
if not (NewPageSize in [1 .. 999]) then
Error(DefaultPageSizeErr);

PageSize := NewPageSize;
end;

procedure GetPageSize(): Integer
begin
if PageSize = 0 then
exit(GetDefaultPageSize());

exit(PageSize);
end;

procedure Reset()
begin
Clear(NextLink);
Clear(PageSize);
end;

procedure GetDefaultPageSize(): Integer
begin
exit(100);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// ------------------------------------------------------------------------------------------------
// 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.Graph;

using System.RestClient;

codeunit 9359 "Graph Pagination Helper"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

procedure ExtractNextLink(HttpResponseMessage: Codeunit "Http Response Message"; var GraphPaginationData: Codeunit "Graph Pagination Data")
var
ResponseJson: JsonObject;
JsonToken: JsonToken;
NextLink: Text;
ResponseText: Text;
begin
if not HttpResponseMessage.GetIsSuccessStatusCode() then begin
GraphPaginationData.SetNextLink('');
exit;
end;

ResponseText := HttpResponseMessage.GetContent().AsText();

// Parse JSON response
if not ResponseJson.ReadFrom(ResponseText) then begin
GraphPaginationData.SetNextLink('');
exit;
end;

// Extract nextLink
if ResponseJson.Get('@odata.nextLink', JsonToken) then
NextLink := JsonToken.AsValue().AsText();

GraphPaginationData.SetNextLink(NextLink);
end;

procedure ExtractValueArray(HttpResponseMessage: Codeunit "Http Response Message"; var ValueArray: JsonArray): Boolean
var
ResponseJson: JsonObject;
JsonToken: JsonToken;
ResponseText: Text;
begin
Clear(ValueArray);

if not HttpResponseMessage.GetIsSuccessStatusCode() then
exit(false);

ResponseText := HttpResponseMessage.GetContent().AsText();

// Parse JSON response
if not ResponseJson.ReadFrom(ResponseText) then
exit(false);

// Extract value array
if not ResponseJson.Get('value', JsonToken) then
exit(false);

ValueArray := JsonToken.AsArray();
exit(true);
end;

procedure ApplyPageSize(var GraphOptionalParameters: Codeunit "Graph Optional Parameters"; GraphPaginationData: Codeunit "Graph Pagination Data")
begin
if GraphPaginationData.GetPageSize() > 0 then
GraphOptionalParameters.SetODataQueryParameter(Enum::"Graph OData Query Parameter"::top, Format(GraphPaginationData.GetPageSize()));
end;

procedure CombineValueArrays(HttpResponseMessage: Codeunit "Http Response Message"; var JsonResults: JsonArray): Boolean
var
ValueArray: JsonArray;
JsonItem: JsonToken;
begin
if not ExtractValueArray(HttpResponseMessage, ValueArray) then
exit(false);

foreach JsonItem in ValueArray do
JsonResults.Add(JsonItem);

exit(true);
end;

procedure IsWithinIterationLimit(var IterationCount: Integer; MaxIterations: Integer): Boolean
begin
if IterationCount >= MaxIterations then
exit(false);

IterationCount += 1;

exit(true);
end;

procedure GetMaxIterations(): Integer
begin
exit(1000); // Safety limit to prevent infinite loops
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ codeunit 9354 "Graph Request Helper"
PrepareRestClient(GraphOptionalParameters);
HttpResponseMessage := RestClient.Send(HttpMethod, GraphUriBuilder.GetUri(), HttpContent);
end;

procedure GetByFullUrl(FullUrl: Text) HttpResponseMessage: Codeunit "Http Response Message"
begin
HttpResponseMessage := RestClient.Get(FullUrl);
end;
}
Loading
0