Thursday, December 26, 2013

How to make your ProgressBar progress on server

In AX 2012 we deal with three types of progress bars:

  • SysOperationProgress
  • SysOperationProgressEmbedded
  • SysOperationProgressServer
that initialized in RunBase.progressInit method.

What concerns the latter that runs on the server side for batch jobs and tasks, I have never seen before that the Progress field changed during batch tasks execution. 



This is because RunbaseProgress Construct method is called without _disableProgressWithoutGUI parameter; nevertheless, it exists in its own New method. This parameter is always TRUE.

I do not know if this option is disabled by purpose having some other developing perspectives in mind, or simply by mistake.

In fact, it is not enough just to extend constructors accordingly to pass this parameter from your own processing class down to SysOperationProgressServer methods. There is another bug in Construct.

This bug does not take into account the caller class that creates its progress bars passing an empty GUID, so that Update method always refreshes the first progress bar found in SysProgress table. In other words, if you started a few batch tasks simultaneously, you will see an interesting progressing: after 100% it starts from zero and so on until the last task finishes. No progressing for the rest of the tasks.

The third bug is in Reset method, which actually run in turn by progress.Kill method (now marked as obsolete). Unfortunately, I do not inderstand the logic of this approach to reset the progress bar when my tasks is finished. From my point of view, it must be really 'killed' it in the end.

All in all, the following changes in forementioned objects fix the problem.


Your class example


// Sample of your class supposed to be processed as a batch
void  processSomething()
{

    // maximum number of lines to be processed
    totalLines = ...;
    // to enjoy our boring user during a few next hours...
    this.progressInit("@SYS59011", totalLines, #AviUpdate, 1, null, false, newGuid()); 
 // create the progressbar on the server side if it is not a client session with a new unique GUID

    while (...)
    {
        ...
    }

    progress.kill(true);
}

RunBase
progressInit


/*
This method is called to initialize the Progress object in runbase.
*/
public void progressInit(
    str                 caption,
    int64               total,
    Filename            animation,
    int                 numOfBars       = 1,
    FormBuildControl    embedded        = null,
    // Begin: Alexey Voytsekhovskiy, bug fix
    boolean             _ifOnSrvBypass = true,
    guid                _callerId       = str2guid('')
    // End: Alexey Voytsekhovskiy, bug fix
    )
{
    if (! progress)
        progress = RunbaseProgress::construct(numOfBars,embedded
        // Begin: Alexey Voytsekhovskiy, bug fix
        // to set progressbar on the server side
                                                                ,_ifOnSrvBypass, _callerId); 
        // End: Alexey Voytsekhovskiy, bug fix

    progress.setCaption(caption);
    progress.setTotal(total);
    progress.setAnimation(animation);
}


RunbaseProgress
construct


public static RunbaseProgress construct(
    int                 _numOfBars  = 1,
    FormBuildControl    _embedded   = null,
    boolean             _ifOnSrvBypass = true,
    // Begin: Alexey Voytsekhovskiy, bug fix
    guid                _callerId = str2guid('')
    // End: Alexey Voytsekhovskiy, bug fix
    )
{
    return new RunbaseProgress(_numOfBars,_embedded, _callerId, -1, DateTimeUtil::minValue(), _ifOnSrvBypass);
}

kill

// Begin: Alexey Voytsekhovskiy, reset bug fix
#obsolete void kill(boolean _toKill = false)
{
    this.reset(_toKill);
}
// End: Alexey Voytsekhovskiy, reset bug fix

reset

// Begin: Alexey Voytsekhovskiy, reset bug fix
void reset(boolean _toKill = false)
// End: Alexey Voytsekhovskiy, reset bug fix
{
    if (oprProgress)
        oprProgress.reset();
    else if (oprProgressServer )
        // Begin: Alexey Voytsekhovskiy,  reset bug fix
        oprProgressServer.reset(_toKill);
        // End: Alexey Voytsekhovskiy, reset bug fix
}

SysOperationProgressServer
reset

// Begin: Alexey Voytsekhovskiy, reset bug fix
public void reset(boolean _toKill = false)
// End: Alexey Voytsekhovskiy, reset bug fix
{
    UserConnection conn;
    SysProgress progress;
    int i;

    if ( bypass )
        return;

    conn = new UserConnection();
    progress.setConnection(conn);

    conn.ttsbegin();
    try
    {
        // delete existing recoreds for this session.
        delete_from progress
        where progress.SessionIdx == sessionIdx &&
            progress.SessionLoginDateTime == sessionLoginDateTime;

        // create records for this session based on number of bars
        for (i=1; i<=numOfBars; i++)
        {
            // Begin: Alexey Voytsekhovskiy, reset bug fix
            // if the task finished, we have to really kill the progress bar
            if(!_toKill)
            {
            // End: Alexey Voytsekhovskiy, reset bug fix
                select forupdate * from progress where progress.CallId == callId && progress.ProgressIndex == i;

                progress.CallId = callId;

                progress.SessionIdx = sessionIdx;
                progress.SessionLoginDateTime = sessionLoginDateTime;

                progress.ProgressIndex = i;
                // any other default values?
                progress.write();
            }
            // add the recid into the map
            progressRecIds[i] = progress.RecId;
            currentCount[i] = -1;
            currentIncrement[i] = 0;
            total[i] = 0;
            text[i] = '';
        }

        caption = '';
        animation = '';

        ticksAtLastUpdate = 0;

        conn.ttscommit();
    }
    catch
    {
        conn.ttsabort();
    }
}

Let me know, if you find other bugs in this functionality.


No comments: