Application Studio - Function Output to Grid - Help Needed

I am attempting to wrap my head around how to pass the output parameter from a function to a data view. In this use case, I am adding a panel grid card to the Activity panel in the Part Tracker module:

The idea is that when the button is clicked, it will pull the top 10 listed customers within the customer table in Epicor. However, what happens currently is the form loads, and the panel grid is grayed out, like it’s not initializing correctly:

Any tips and tricks on how to troubleshoot this would be greatly appreciated.

Attached, you will find the simple library and function I created for this attempt, as well as screenshots that denote the steps I’ve taken thus far.

FunctionToGridTest.efxb (2.0 KB)

C# From Function:

using System.Text.Json;
using System.Collections.Generic;
using System.Linq;

var result = new List<Dictionary<string, object>>();

var customers = Db.Customer
    .Where(c => c.Company == Session.CompanyID)
    .Take(10);

foreach (var cust in customers)
{
    result.Add(new Dictionary<string, object> {
        { "CustID", cust.CustID },
        { "Name", cust.Name },
        { "City", cust.City }
    });
}

// Convert to JSON string
this.Result = System.Text.Json.JsonSerializer.Serialize(result);

Data View:

Grid properties:

Event:

Control:

Kinetic-Function:

Kinetic-Function Response Parameter:

“Parse from response path” should be the name of your dataset in your function signature on the “Response Parameters” side. For example, here, “Parse from response path” would be “outDS”


That is a DataSet, a DataSet contains DataTables (or can be empty/null), so at some point in your function would have needed to add a DataTable, containing DataRows, with DataColumns and Values to your dataset, to have data in it. that DataTable has a name, and that name goes into “Parameter Name”. It would have been set up in the code with "DataTable xyz = new DataTable(“Result”); or similar, if your DataTable is named “Result”

The grid being greyed out is due to setting up an epbinding at the panel-card-grid level, remove this and put the epbinding one level deeper at the “grid model” level.

Looking at your function, I see that you are operating with a List<Dictionary<string, object>> and serializing to json.

It will be easier if you do something like this:

DataSet outDS = new DataSet("outDS");
DataTable dt = new DataTable("Result");
dt.Columns.Add("CustID", typeof(string);
dt.Columns.Add("Name", typeof(string);
dt.Columns.Add("City", typeof(string);
foreach (DataRow r in Db.Customer.Where(c => c.Company == Session.CompanyID).Take(10)) {
  DataRow nr = dt.NewRow()
  nr["CustID"] = r.CustID;
  nr["Name"] = r.Name;
  nr["City"] = r.City;
  dt.Rows.Add(nr);
}
outDS.Tables.Add(dt);

With this, there’s also no dependency on newtonsoft.

Right now, Newtonsoft is serializing your output into a JArray vs. a JObject, so it is a flat array of objects with no name. When you attempt to address it as “Result” it can’t find that. If you monitor the output of your function in dev tools to see the REST JSON, you can see your structure output is something like this:

[
  {
    "CustID": "ABC001",
    "Name": "Alpha Corp",
    "City": "Chicago"
  },
  {
    "CustID": "XYZ132",
    "Name": "Beta LLC",
    "City": "Dallas"
  }
]

When, what application studio expects to see is a DataSet/DataTable/DataRow nested JSON representation, something like this:

{
  "outDS": {
    "Result": [
      {
        "CustID": "ABC001",
        "Name": "Alpha Corp",
        "City": "Chicago"
      },
      {
        "CustID": "XYZ132",
        "Name": "Beta LLC",
        "City": "Dallas"
      }
    ]
  }
}
3 Likes

I was able to implement the changes you recommended and the grid is no longer grayed out.

Now though I am getting an error in the console but do not understand how to start troubleshooting this. It errors every time I click the button so at least works the way it should:

Any recommendations on how to interpret this?

It doesn’t tell you anything useful, these are errors generated from the MetaFX JavaScript that lies underneath Kinetic, not a direct issue with your set up, but some issue with your set up triggered that error in underlying code. We could get some meaning from it with attaching a debugger, but if you look higher in the console, probably there is more info that is useful. Ensure that you have debugging on (ctrl-alt-8) before pressing the get button.

1 Like

Here’s the new error:

I’m defiantly adding that crlt+alt+8 command to my spell book.

It looks like the function is running but not returning expected data.
With ctrl-alt-8 active (debug mode on), while clicked somewhere in your app window (not in the dev tools window) press ctrl-alt-v. this will populate the dev tools console window with your dataviews in memory. There will be system dataviews and application dataviews.

We would expect to see your output in the Application DataViews → Results DataView, use the > arrow next to it and > again on the array(xxx) to see the rows inside. If there isn’t any, data didn’t get transferred from the function to the dataview correctly.

You should expect to see something like this:

Check in System DataViews → actionResult, what’s there? all output from the function (regardless of what you’ve set up in the REST Response Parameters) should be contained here.

When I run a function that outputs a DataSet named outDS, with a Table named “File”, two columns named “LineNum” (int32) and “Text” (string), with n number of rows, this is what i see:

If you don’t see expected output here, the problem is not with anything on app studio side, the problem is on function side. Get the function to output what you expect here first (if you have data here, please show us a screenshot) - then you can work on getting the output into the DataView correctly.

2 Likes

So I do not see anything in the results data view.

I went ahead and put the function in an access scope and gave it an api key for testing. I see the data I expect in there for my 10 customers. However, I do not see data in our other return sections:

I also had to made a few small changes to the code you provided to get me to this point:

using (DataSet outDS = new DataSet("outDS"))
{

//DataSet outDS = new DataSet("outDS");

DataTable dt = new DataTable("outResult");

dt.Columns.Add("CustID", typeof(string));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("City", typeof(string));

// Fix: Use correct entity type
foreach (var customer in Db.Customer.Where(c => c.Company == Session.CompanyID).Take(10)) {
    DataRow nr = dt.NewRow();
    //string custxxx = customer.CustID;
    nr["CustID"] = customer.CustID;
    nr["Name"] = customer.Name;
    nr["City"] = customer.City;
    dt.Rows.Add(nr);
    
    //messagebox in bpm
    //this.PublishInfoMessage(custxxx, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
}

outDS.Tables.Add(dt);
this.outDS = outDS;
}

Response Parameters:

I feel like I’m really close to having this working.

Thanks a ton for helping me learn this stuff! You’ve been a great help!

With what you’ve shown, if you run this function from the button in app studio with debug mode on, then check the dataviews, I would expect to see your data now in System Dataviews → actionResult → outDS → outResult → (rows of data)

Assuming that it is there, and you have set up a “dvCustomers” DataView to receive this info (you could have no fields at all in this dataview, it isn’t necessary to receive the output) - all you need is an empty DataView with a name.

Note: if you have ever clicked that “dataset” button, there used to be a bug in app studio that would corrupt the kinetic-function component and you had to delete the component and rebuild it without clicking this button, if you see everything is looking right and still not working, try to delete the component and re-create it at the end.

(note that in your function response parameters, you only need outDS in this case. outSuccess and outResult were there because I took the screenshot from another function I made, sorry for confusion! in this case all we need the function to output is the outDS object, and the output from your API call to it looks exactly right.)

I use an outString in the Response section of the function so that I can talk to myself as i’m walking through things, to validate my data is what i expect it to be in the middle of the function.

For example, for your code, if you wanted to make sure that your customerid is being looked up correctly from your LINQ query .Where(xxx)…

nr["CustID"] = customer.CustID;
outResult += $"Customer: {customer.CustID}\n";

now, every time it hits that section of code, it’s going to build on to that string, listing all of the customers that matched in your loop. when you run it from app studio, you will see the output here:

This is useful for debugging, you can output variables along the way and help to figure out which line is where your problem is.

1 Like

The rebuilding of the Kinetic-Function widget did the trick!!! :tada::tada::tada::tada::tada::tada:

Without you knowing that bit, I don’t think I would have gotten that figured out.

Do you know of a place where other bugs like this are cataloged? Or other good reads on application studio reside? I’ve been chiseling through the tech docs on EpicWeb and have been going through @hmwillett write-up Kinetic Control Compendium.

Thanks again for helping me wrap my head around this. Solution goes to you!

2 Likes

I wish! The info is sporadically located throughout epiusers, or gleaned from experience bashing your head against the brick wall until it budges. (and then, hopefully also posted to epiusers when you recover.) It would make a nice roll-up like Hannah’s post! “Kinetic Quirks and Gotchas”

1 Like

@GabeFranco I feel like I’m working on a very similar issue but trying to put the returned object from the function in a different place. I’ve got a function that returns an object called metadata and I want to use the metadata object as a parameter in a rest-erp widget for the docstardownloadfile method.

{
    "MetadataDS": {
        "metadata": [
            {
                "_TableName": "Erp.Project",
                "_TableSysRowID": "34b2d142-60fc-420f-bab0-e7c3c2dbc2d7",
                "ContentItems": "1"
            }
        ]
    }
}

Should I save this into a dataview? If so how do I point to the dataview as an object?

Create a “metadata” dataview. (just give it a name, no set up other than that)

Set your kinetic-function component response parameters like this:

Since you are only returning one row of data, don’t need to worry about using a row-find/row-current-set to pick a particular row of data (if you had more rows, you would!)

now, to use any of the values in the currently selected row of metadata in app studio, reference it as the standard {dataview.fieldname} - in this case {metadata._TableName} would go into the rest-erp parameter.

The overarching concepts here are:

DataSet: has a name, Contains DataTables
DataTable: has a name, Contains DataRows
DataRow: has a 0-based numeric index, Contains Columns, Name/Value/DataType combinations.

To get a DataSet into app studio, you need the following:
DataView: a basic DataView set up with a name only. Think of this as equivalent to a DataTable, but much more “dynamic” - it can have a name only, and the columns/rows/etc. dynamically created as the data is loaded into it. A DataView can have multiple rows, and you’d need a row-find/row-current-set pair of components to locate the correct row to work with (by default, when data is loaded, the row is set to 0, the data you want might be on row 7 of 15)
Kinetic-Function Widget:
“Parse From Response Path” <— what’s the name of the “root object” your function outputs? Which in this case is the DataSet? Above: “MetadataDS”
“Parameter Name” <— what’s the name of the first child object of the parent root object output from your function? In this case since the parent is a DataSet named “MetadataDS”, the child is a DataTable named “metadata”. Above: “metadata”
“View Name” <---- a dataview to load the data into. This can actually be dynamically created at runtime, it is valid not to manually create it. But it will end up in “System Views” if you don’t manually create it in application studio.

The problem seems to be what the Rest-erp widget is expecting for metadata, it doesn’t want a specific field from the dataview (ie Erp.Project) it wants the object that holds all of it. I need to send an object and not a value. I think even an empty object would work but I couldn’t figure out how to do that either.

{
                "_TableName": "Erp.Project",
                "_TableSysRowID": "34b2d142-60fc-420f-bab0-e7c3c2dbc2d7",
                "ContentItems": "1"
            }

Oh, i’ve not tried to do that before.
My approach would be to try and find an epicor-developed module that does this behavior, load it into app studio, and see what they are doing. May also help to create a layer that enables debugging on load, and trace the events.

The use case is being able to view attachments that are stored in ECM from a custom dashboard. Unfortunately the attachment details are relatively obscured on all of the standard apps. I can see the network calls, but not the event setup. When the standard app calls DocStarDownloadFile this is what it sends

{xFileRefNum: 24, metadata: {}}
metadata
: 
{}
xFileRefNum
: 
24

If I look at it from the Kinetic UX Trace Utility it looks like this:

 "body": {
    "xFileRefNum": 24,
    "metadata": {}
  }```

Oh man, relatively obscured indeed, you’d probably be digging through some extensive javascript to locate the code that handles this stuff, i think.

This is where I would suggest going with a function to handle your logic, since you can call the BOs and send/return data that isn’t in a standard dataview/datatable/dataset format, and normalize it into that format to return it if needed. (what I mean is, don’t pass your data out of the function into the rest-erp widget, directly perform the rest-erp widget activity within the function, and return the resulting output instead)

I might suggest taking a look at the code behind the function library “FunctionTools” in the dashboard “Dev Tools” I posted, I do this sort of manipulation with BOs extensively for use in a dashboard front end.

I have your Dev Tools installed but I’ve been a little terrified to look under the hood. I’m relatively confident that I could put together a function that would return the base64 file, but I do not even begin to know what to do with that to show it to someone. I guess if the attachments are pdf I could use the pdf viewer, or the workaround @josecgomez just posted about?

Yes, that would be the path I would take.
Return base64 from the function, feed it into the the workaround (it’s epicor, so is it a workaround or it SOP…?)
It’s not too bad to work with the BO’s directly, only the correct references and a few lines of code.
What you are feeding into DocStarDownloadFile is probably well defined as an object already too, if you include the right reference(s).
It looks like the expected input that the api is looking for is a JArray. You might be able to use javascript to build the correctly formed json in app studio to pass it directly as a parameter.

I can’t remember the exact syntax, in the rest-erp parameter box it’d be something like this, you can find some examples here if i recall.

#_JSON.stringify( { _TableName: {metadata._TableName}, _TableSysRowID: {metadata._TableSysRowID}, ContentItems: {metadata.ContentItems} } )_#

I think maybe its this:

#_JSON.stringify(trans.dataView('metadata').viewData)_#

I got @josecgomez’s website widget to work great. I tweaked his row update a little to handle more file types since I expect many attachments to be pictures. Thank you both for the help

2 Likes