Dynamics 365 Business Central: How to run Job Queue via AL (Debug Job Queue in Sandbox???)

Dynamics 365 Business Central

Hi, Readers.
Today I would like to talk about how to run Job Queue via AL.

A job queue is basically an abstraction that uses the task scheduler from the platform to enable end users to view, create, or modify jobs that are set to run in the background. These jobs can be set to run on a recurring schedule.

I was asked this last week, is there any way to run the Job Queue from our extension? Emm…My first thought was that there was no need to do so. As you might know, Job queues in Business Central enable users to schedule and run specific report and codeunit. Generally, we don’t need to execute Job Queue, just run the set report and codeunit directly in the code.

I later realized that they were testing whether they could debug Job Queue in Sandbox.

Okay, let’s change the topic and see if we can debug Job Queue in Sandbox. Microsoft provides the breakOnNext parameter for the background session, such as job queues.
Launch.json file:

More details:

But unfortunately, Background session is not supported in Sandbox.
Attach support:

Update 2024.01.16: We can now attach to the next background session in a sandbox environment. More details: Supported Attach configurations. (The following method still works)

While this is not a way to fully emulate the Job Queue, they want to run the Job Queue directly in the code.

I briefly investigated it. This looks doable. We can refer to the Run once (foreground) action on the page 672 “Job Queue Entries” page.

You can also debug directly using this method if an error occurs in your setting report or codeunit of Job Queue.
For example,

In addition, even if there is no debugging, the specific error message can be confirmed after the Job Queue is executed.

PS: This error will also be displayed in the Latest Error.

Next, let’s see if this action can be ported to other pages.
For testing, I created a new Codeunit (automatically create items) and set it in the Job Queue Entry.

Primary Key:

Create a new action on the Item List page to run the Job Queue.

Test Video: This looks good.

Source Code:

pageextension 50111 MyExtension extends "Item List"
{
    actions
    {
        addafter("Item Journal")
        {
            action(RunJobQueue)
            {
                Caption = 'Run Job Queue';
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                Image = ExecuteBatch;

                trigger OnAction()
                var
                    JobQueueManagement: Codeunit "Job Queue Management";
                    JobQueueEntry: Record "Job Queue Entry";
                begin
                    if JobQueueEntry.Get('55f18429-d645-4444-a42a-b49bb15a3059') then
                        JobQueueManagement.RunJobQueueEntryOnce(JobQueueEntry);
                end;
            }
        }
    }
}

Next question, is it possible to hide this Confirm window?

I checked the RunJobQueueEntryOnce function, but unfortunately Microsoft didn’t leave us an Event.

However, we can copy this code and customize a new function.

Let’s test it again.

Source Code: You can also make other required changes.

pageextension 50111 MyExtension extends "Item List"
{
    actions
    {
        addafter("Item Journal")
        {
            action(RunJobQueue)
            {
                Caption = 'Run Job Queue';
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                Image = ExecuteBatch;

                trigger OnAction()
                var
                    //JobQueueManagement: Codeunit "Job Queue Management";
                    JobQueueEntry: Record "Job Queue Entry";
                begin
                    if JobQueueEntry.Get('55f18429-d645-4444-a42a-b49bb15a3059') then
                        //JobQueueManagement.RunJobQueueEntryOnce(JobQueueEntry);
                        ZYRunJobQueueEntryOnce(JobQueueEntry);
                end;
            }
        }
    }

    var
        RunOnceQst: label 'This will create a temporary non-recurrent copy of this job and will run it once in the foreground.\Do you want to continue?';
        ExecuteBeginMsg: label 'Executing job queue entry...';
        ExecuteEndSuccessMsg: label 'Job finished executing.\Status: %1', Comment = '%1 is a status value, e.g. Success';
        ExecuteEndErrorMsg: label 'Job finished executing.\Status: %1\Error: %2', Comment = '%1 is a status value, e.g. Success, %2=Error message';
        JobQueueEntriesCategoryTxt: Label 'AL JobQueueEntries', Locked = true;
        RunJobQueueOnceTxt: Label 'Running job queue once.', Locked = true;

    procedure ZYRunJobQueueEntryOnce(var SelectedJobQueueEntry: Record "Job Queue Entry")
    var
        JobQueueEntry: Record "Job Queue Entry";
        JobQueueLogEntry: Record "Job Queue Log Entry";
        SuccessDispatcher: Boolean;
        SuccessErrorHandler: Boolean;
        Window: Dialog;
        CurrentLanguage: Integer;
        Dimensions: Dictionary of [Text, Text];
    begin
        //if not Confirm(RunOnceQst, false) then
        //exit;

        Window.Open(ExecuteBeginMsg);
        SelectedJobQueueEntry.CalcFields(XML);
        JobQueueEntry := SelectedJobQueueEntry;
        JobQueueEntry.ID := CreateGuid();
        JobQueueEntry."User ID" := copystr(UserId(), 1, MaxStrLen(JobQueueEntry."User ID"));
        JobQueueEntry."Recurring Job" := false;
        JobQueueEntry.Status := JobQueueEntry.Status::"Ready";
        JobQueueEntry."Job Queue Category Code" := '';
        clear(JobQueueEntry."Expiration Date/Time");
        clear(JobQueueEntry."System Task ID");
        JobQueueEntry.Insert(true);
        Commit();

        CurrentLanguage := GlobalLanguage();
        GlobalLanguage(1033);

        Dimensions.Add('Category', JobQueueEntriesCategoryTxt);

        Dimensions.Add('Id', Format(JobQueueEntry.ID, 0, 4));
        Dimensions.Add('ObjectType', Format(JobQueueEntry."Object Type to Run"));
        Dimensions.Add('ObjectId', Format(JobQueueEntry."Object ID to Run"));
        Dimensions.Add('Status', Format(JobQueueEntry.Status));
        Dimensions.Add('IsRecurring', Format(JobQueueEntry."Recurring Job"));
        Dimensions.Add('EarliestStartDateTime', Format(JobQueueEntry."Earliest Start Date/Time"));
        Dimensions.Add('CompanyName', JobQueueEntry.CurrentCompany());

        Session.LogMessage('0000FMG', RunJobQueueOnceTxt, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All, Dimensions);
        GlobalLanguage(CurrentLanguage);

        // Run the job queue
        SuccessDispatcher := Codeunit.run(Codeunit::"Job Queue Dispatcher", JobQueueEntry);

        // If JQ fails, run the error handler
        if not SuccessDispatcher then begin
            SuccessErrorHandler := Codeunit.run(Codeunit::"Job Queue Error Handler", JobQueueEntry);

            // If the error handler fails, save the error (Non-AL errors will automatically surface to end-user)
            // If it is unable to save the error (No permission etc), it should also just be surfaced to the end-user.
            if not SuccessErrorHandler then begin
                JobQueueEntry.SetError(GetLastErrorText());
                JobQueueEntry.InsertLogEntry(JobQueueLogEntry);
                JobQueueEntry.FinalizeLogEntry(JobQueueLogEntry, GetLastErrorCallStack());
                Commit();
            end;
        end;

        Window.Close();
        if JobQueueEntry.Find() then
            if JobQueueEntry.Delete() then;
        JobQueueLogEntry.SetRange(ID, JobQueueEntry.id);
        if JobQueueLogEntry.FindFirst() then
            if JobQueueLogEntry.Status = JobQueueLogEntry.Status::Success then
                Message(ExecuteEndSuccessMsg, JobQueueLogEntry.Status)
            else
                Message(ExecuteEndErrorMsg, JobQueueLogEntry.Status, JobQueueLogEntry."Error Message");
    end;
}

END

Hope this will help.

Thanks.

ZHU

コメント

Copied title and URL