language
Call Us: 1-800-497-0151

Blog

Making the Data Management Progress Dialog Useful To Users

  • , Consultant

I’m not a fan of progress dialogs. Why? I think most of them lie. 3 seconds to 98% and then 10 minutes for the last 2%. WUT?!? Let’s do better. Let’s use the OneStream Data Management Progress Dialog to actually inform the user, to tell the truth about the running task.

While we’re at it, let’s empower the user to actually cancel a running task, and have that task stop.

If you’ve built any kind of longer-running Data Management process inside OneStream, you know, the kind where something that loops through months or calls out to external systems…. Then you’ve probably thought it would be useful to provide useful feedback while it runs.

That’s where BRApi.TaskActivity.UpdateRunningTaskActivityAndCheckIfCanceled comes in. It lets you send status messages to the Data Management dialog and, just as important, check if the user has decided to cancel the process.

The basic pattern is simple: call this method regularly throughout your code, pass in a message that describes what you're doing, and use the true/false return value to decide whether to bail out.

I don’t enjoy typing 60+ character function names that are buried several objects deep in a library.

And BRApi.TaskActivity.UpdateRunningTaskActivityAndCheckIfCanceled just that sort of ugly. It just needs an API. So let’s write one. In this case it’s a simple 1 line wrapper method called UpdateDMDialog(message As String). The function returns the same boolean as our 60+ character library function telling whether the user has clicked cancel. So if it returns true, I know the user cancelled the process and I can exit my code too. That gives users a way to stop the process without killing the server or waiting for a timeout.

See the code below for a mock working example of how to use BRApi.TaskActivity.UpdateRunningTaskActivityAndCheckIfCanceled

The real value here is in the messages you send. Think less “30% complete” and more “Fetching records for May…” or “Waiting on NetSuite job 12734…”. Anything that shows what’s happening and where you are in the process gives users confidence or at least some idea whether they should go grab coffee or cancel. You can even include record counts, task IDs, or API latency if you want to get fancy.

I use this pattern in data integrations, file imports, pretty much anywhere things might take a while or fail mid-process. It’s lightweight, it gives the user control, and it’s way better than letting them sit there staring at a spinning dialog tha fibs about actual progress. Drop a few UpdateDMDialog calls in your loop, and viola! Happy, informed user.

C#

public class DataImporter
{
    private readonly object si;
    private readonly object _args;
    private readonly string _dataType;
    private decimal _progress;

    public DataImporter(object sessionInfo, object args, string dataType)
    {
        si = sessionInfo;
        _args = args;
        _dataType = dataType;
        _progress = 0m;
    }

    private bool UpdateDMDialog(string message)
    {
        return BRApi.TaskActivity.UpdateRunningTaskActivityAndCheckIfCanceled(si, _args, message, _progress);
    }

    public async Task<DataTable> GetData(string yearInput)
    {
        var response = new List<string>();
        long totalRecords = 0;

        UpdateDMDialog($"Starting {_dataType} import for year {yearInput}");

        for (int month = 1; month <= 12; month++)
        {
            string monthStr = $"{yearInput}-{month:D2}";
            UpdateDMDialog($"Starting {monthStr}...");

            string taskId = await Mock_DataRequestStart(monthStr);
            await Mock_DataRequestStatus(taskId);

            var result = await Mock_DataRequestComplete(monthStr, taskId);

            _progress = month * 8.33m;
            UpdateDMDialog($"Finished {monthStr} — {result.RecordCount} records");

            if (result.Data != null)
            {
                if (response.Count == 0)
                    response.AddRange(result.Data);
                else
                    response.AddRange(result.Data.Skip(1));

                totalRecords += result.RecordCount;
            }

            if (UpdateDMDialog($"Imported {monthStr}")) return new DataTable();
        }

        var table = Mock_ToDataTable(response);
        string preview = JsonConvert.SerializeObject(
            table.AsEnumerable().Take(5).Select(r => r.ItemArray), Formatting.Indented);

        UpdateDMDialog($"All months complete. Total records: {totalRecords}");
        BRApi.ErrorLog.LogMessage(si, $"Preview:\n{preview}");

        return table;
    }

    // Mock helpers
    private Task<string> Mock_DataRequestStart(string month)
        => Task.FromResult(Guid.NewGuid().ToString());

    private Task Mock_DataRequestStatus(string taskId)
        => Task.Delay(200);

    private Task<DataResult> Mock_DataRequestComplete(string month, string taskId)
        => Task.FromResult(new DataResult
        {
            Data = new List<string> { "header", $"data for {month}" },
            RecordCount = 1
        });

    private DataTable Mock_ToDataTable(List<string> rows)
    {
        var table = new DataTable();
        table.Columns.Add("Content", typeof(string));
        foreach (var row in rows)
            table.Rows.Add(row);
        return table;
    }

    private class DataResult
    {
        public List<string> Data { get; set; }
        public long RecordCount { get; set; }
    }
}

VB.NET

Public Class DataImporter
    Private ReadOnly si As Object
    Private ReadOnly _args As Object
    Private ReadOnly _dataType As String
    Private _progress As Decimal

    Public Sub New(sessionInfo As Object, args As Object, dataType As String)
        Me.si = sessionInfo
        Me._args = args
        Me._dataType = dataType
        Me._progress = 0D
    End Sub

    Private Function UpdateDMDialog(message As String) As Boolean
        Return BRApi.TaskActivity.UpdateRunningTaskActivityAndCheckIfCanceled(si, _args, message, _progress)
    End Function

    Public Async Function GetData(yearInput As String) As Task(Of DataTable)
        Dim response As New List(Of String)()
        Dim totalRecords As Long = 0

        UpdateDMDialog($"Starting {_dataType} import for year {yearInput}")

        For month As Integer = 1 To 12
            Dim monthStr As String = $"{yearInput}-{month:D2}"
            UpdateDMDialog($"Starting {monthStr}...")

            Dim taskId As String = Await Mock_DataRequestStart(monthStr)
            Await Mock_DataRequestStatus(taskId)

            Dim result As DataResult = Await Mock_DataRequestComplete(monthStr, taskId)

            _progress = month * 8.33D
            UpdateDMDialog($"Finished {monthStr} — {result.RecordCount} records")

            If result.Data IsNot Nothing Then
                If response.Count = 0 Then
                    response.AddRange(result.Data)
                Else
                    response.AddRange(result.Data.Skip(1))
                End If

                totalRecords += result.RecordCount
            End If

            If UpdateDMDialog($"Imported {monthStr}") Then
                Return New DataTable()
            End If
        Next

        Dim table As DataTable = Mock_ToDataTable(response)
        Dim preview As String = JsonConvert.SerializeObject(
            table.AsEnumerable().Take(5).Select(Function(r) r.ItemArray),
            Formatting.Indented
        )

        UpdateDMDialog($"All months complete. Total records: {totalRecords}")
        BRApi.ErrorLog.LogMessage(si, $"Preview:{vbLf}{preview}")

        Return table
    End Function

    ' Mock helpers
    Private Function Mock_DataRequestStart(month As String) As Task(Of String)
        Return Task.FromResult(Guid.NewGuid().ToString())
    End Function

    Private Function Mock_DataRequestStatus(taskId As String) As Task
        Return Task.Delay(200)
    End Function

    Private Function Mock_DataRequestComplete(month As String, taskId As String) As Task(Of DataResult)
        Dim result As New DataResult With {
            .Data = New List(Of String) From {"header", $"data for {month}"},
            .RecordCount = 1
        }
        Return Task.FromResult(result)
    End Function

    Private Function Mock_ToDataTable(rows As List(Of String)) As DataTable
        Dim table As New DataTable()
        table.Columns.Add("Content", GetType(String))
        For Each row As String In rows
            table.Rows.Add(row)
        Next
        Return table
    End Function

    Private Class DataResult
        Public Property Data As List(Of String)
        Public Property RecordCount As Long
    End Class
End Class

Contact MindStream Analytics

Want to learn more about and OneStream Software? Please complete the form below and we'll get back to you shortly.


Featured Webinar

OneStream Quick Views

Unlock the power of OneStream Quickviews and elevate your financial planning and analysis (FP&A) process with this insightful webinar hosted by Erick Lewis of MindStream Analytics.

How OneStream Quickviews put the A in FP&A

Partner SpotLight

OneStream Diamond Partner

OneStream CPM

OneStream aligns to your business needs and changes more quickly and easily than any other product by offering one platform and one model for all financial CPM solutions. OneStream employs Guided Workflows, validations and flexible mapping to deliver data quality confidence for all collections and analysis while reducing risk throughout the entire auditable financial process.

OneStream Profile