:dumpster_fire: HowTo -> Kinetic BAQ Custom Actions - AKA App Studio Hides Stuff From You

Ok, well this was a very interesting dive.

This started here:

And he wanted to run a custom action and pass the queryResultDataset so it would work properly.

Well here is everything I found out.

First → App Studio Lies :dumpster_fire:

First I created a UBAQ, on ABCCode of course. You know, the default Test Table.
I set it so I could update Count Frequency. I then added a Custom Action called Test.
I created a base bpm under Custom Actions, checked my condition on the action ID,
and output some debug data.

The code for that was basically a condition block to only fire if the actionID was “Test” & this code. →

var changedRows = queryResultDataset.Results.Where(x => !String.IsNullOrEmpty(x.RowMod));


var unchangedRows = queryResultDataset.Results.Where(x => String.IsNullOrEmpty(x.RowMod));


var changedRowsString   = "Changed Rows:" + Environment.NewLine + JsonConvert.SerializeObject(changedRows, Formatting.Indented);
var unchangedRowsString = "Unchanged Rows:" + Environment.NewLine + JsonConvert.SerializeObject(unchangedRows, Formatting.Indented);



InfoMessage.Publish(changedRowsString + Environment.NewLine + Environment.NewLine + unchangedRowsString);

All good so far.

I then proceeded to use the wizard in app studio to create a new app.
Told it the baq was updateable and all that jazz.
It created my app, and I had two events I could look at.

  • GetNew
  • Update

So first things first, I thought, I’ll just copy one and change it.
So I did, I copied one, got everything pointed correctly, and found out some trash.

App Studio hides things. This is not a normal rest-erp / rest-kinetic.
There is nothing you can change here.

I mean you can try, but that will just make things worse.

Here is what the json for that looks like:

{
    "id": "btn_Update_MyView",
    "trigger": {
        "type": "Control",
        "target": "btn_Update_MyView",
        "hook": "onClick"
    },
    "actions": [
        {
            "type": "dataview-set-current",
            "value": "MyView"
        },
        {
            "type": "rest-erp",
            "param": {
                "requestMethod": "POST",
                "erpRequestMethod": "UpdateBaq",
                "erpBaqArgs": {
                    "baqId": "abc",
                    "options": {
                        "populateDataView": true,
                        "updateOptions": {
                            "operation": "UpdateBaq",
                            "viewName": "MyView"
                        }
                    }
                }
            }
        }
    ]
}

And here is a normal one for reference:

{
    "trigger": {
        "type": "Control",
        "target": "btn_CustomActionTest_MyView",
        "hook": "onClick"
    },
    "actions": [
        {
            "type": "dataview-set-current",
            "value": "MyView"
        },
        {
            "type": "rest-erp",
            "param": {
                "requestMethod": "POST",
                "erpRequestMethod": "ErpPostWithDataViewProcessing",
                "methodParameters": [
                    {
                        "field": "queryID",
                        "type": "string",
                        "value": "abc"
                    }
                ],
                "svc": "Ice.BO.DynamicQuerySvc",
                "svcPath": "GetByID"
            },
            "caption": "rest-kinetic"
        }
    ],
    "id": " btn_CustomActionTest_MyView",
    "description": "Test Custom Action",
    "customizable": true,
    "disable": false
}

Anyway, those options are not exposed in App Studio, so you can’t set them.
Of course, I did not accept that, so I tried to set them myself in the json…

Started looking in the abomination of javascript, and found I could change
erpBaqArgs.options.updateOptions.operation to UpdateBaqCustomAction

However, I could not figure out how to set the actionID. I tried everything, and the abomination was no help. If someone knows… @olga ? lol let me know.

Anyway, it doesn’t matter too much, because we can do this two other ways…

Way #1 → Kinetic-Baq widget (Has caveats lol)


Set BAQ Id to your BAQ
Set View Name to the dataview that holds your BAQ Data
Set Mode to update

Then on to the BAQ Update Options →


Set Operation to customAction
Set Action Id to your Custom Action Id

Now this has a caveat. It won’t send a Before Image.
Most things will still work, as some kind of EpiMagic Voodoo will create one for you on the fly.
However, it lies, as it just copies the updated row to one without a RowMod.
That means conditions like field has changed from won’t work properly.

See:


:dumpster_fire:

Fortunately, there is another option…

Way #2 → Rest-Erp widget (Has some weirdness, but works fine lol)


Set Service Name to Ice.BO.DynamicQuerySvc
Set Service Operation to RunCustomActionByID

Then move on to the Method Parameters:


Set queryID to your BAQ


Set actionID to your Custom Action ID


DELETE the queryResultDataset parameter!! Yes, bye bye.

Move on to the Kinetic Rest Arguments:


Set Parameter Path to queryResultDataset
Set Parameter Name to Results (almost got me lol)
Set View Name to the dataview that holds your BAQ Data

Now for the wierdness, not really wierd, it just can’t be gotten rid of.
It will add an empty ds object, that will not cause any issues.
It is empty because we put our queryResultDataSet at the root, instead of inside ds.
(I can’t explain that well, don’t ask.)

Notice this one has a PROPER Before Image

Done

And that’s it. There are some other options you can play with in there, like sending all rows etc.
I’ll leave that up to y’all.

12 Likes

When are they gonna ditch the BeforeImage stuff and go w a standard like ETags?

This all sounds like a mess! :face_vomiting:

What about a generic function that takes the query result dataset baqid and action ID?

Is that any better? Probably not Bleh :nauseated_face:

@klincecum what if you make a classic dashboard with an action and then try to uplift it I wonder if Epicor will crate the event for you :thinking:

3 Likes

This should say: For the Field Name queryID, set the Field Value to your BAQ ID.
This is great! It works perfectly with my original code! Thank you for your volunteer efforts to make this extremely expensive software useable!

4 Likes

Was gonna do that next. Probably cleaner.

Just wanted follow the rabbit.

3 Likes

Explain yourself.

3 Likes

I doubt they’ll ever do it too much work it would require keeping the OG version server side somehow.

It means the state of the dataset would have to be kept on the server

4 Likes

It DID!

{
    "id": "btn_Test_abc_0_0",
    "trigger": {
        "type": "Control",
        "target": "btn_Test_abc_0_0",
        "hook": "onClick"
    },
    "actions": [
        {
            "type": "rest-erp",
            "param": {
                "requestMethod": "POST",
                "erpRequestMethod": "UpdateBaqCustomAction",
                "erpBaqArgs": {
                    "baqId": "abc",
                    "options": {
                        "populateDataView": true,
                        "updateOptions": {
                            "operation": "UpdateBaqCustomAction",
                            "actionID": "Test",
                            "viewName": "abc_0_0"
                        }
                    }
                }
            }
        }
    ]
}
5 Likes

So that’s nice to know, but unexposed properties is bad form.
At least we know what they are if you want to manually edit the json and do it like Epicor.

2 Likes

Yeah they have this all over the place it makes me uber mad.

1 Like

Angry Inside Out GIF by Disney Pixar

2 Likes

It is better.





CallService<DynamicQuerySvcContract>(dq =>
{
    Results = dq.RunCustomActionByID(queryID, actionID, queryResultDataSet);    
});
5 Likes

I likes this much better! Nice job sir

@Epicor take note! :rofl::rofl:

3 Likes

Unless field-has-changed-from logic moves client-side, no?

1 Like

Btw, does anyone have docs on how kinetic handles concurrency? I mean if my OG row has been changed by another user since I grabbed it, my update fails, correct? Or does it do some sorta server-side field-level merge magic?

If fails, then ETag does the same without passing two copies of the same record over the wire both ways. seems like wasteful, legacy, anti-perf, :man_shrugging:

The SysRevID field handles that.

4 Likes

IMO this should never be handled client side (even today) epicor does it badly. Having your business logic rely on field change triggers and that entire logic living on an unsecured web browser that anyone can mess with is a recipe for disaster.

Epicor has gotten away with it for years by passing the Before Image in the Fat Client cause it was written in C# and while not impossible it limits the ability to mess with the BO Payload.

However on the browser a little F12 and a bit of a proxy extension and people could get up to some really naughty things. Idealy the Before payload should be signed if sent to the client and verified server side, or just store the entire state server side. Neither of these choices exist in Epicor today though and I doubt they’ll be implemented.

5 Likes

Awkward Season 4 GIF by The Office

1 Like

I must be missing something. If SysRevID in before image doesn’t match the server then your change is blocked. If it does match, then the server knows the before image. How is passing it back and forth helping even for field-changed-from logic? Just saves a db read?
Sounds like we’re both spending banwidth to save on their server-side cache efforts.

It’s not that the entrie logic lives in one place or the other. I’m a fan of dividing it up for a better UX.

Screen_Validate methods execute on the client, Field_Validate methods on the on the tier where the field is changed (both server and client), Table_Validate methods on the server. It shouldn’t take a server round trip (not to mention having double the payload for no reason) to find out I’m missing a required field.

Can we at least agree that AppStudio Rules & Events could rather easily be improved for easy screen/field validation to save server trips? Like in rules I can set the style to Warning which makes a control yellow, but I can’t easily display a meaningful warning before save(?) And I can do condition > condition > condition on BeforeUpdate event to check for required fields, but this has no connection to rules(?)

Imagine if there was a validation errors collection that rules can write to and the built-in BeforeUpdate event surfaced those and cancelled the save. Server data rules come down to client by default, user defined rules if you want to, oh and, function rules should allow javascript. :face_with_tongue:.

Server is the final judge, of course but it’s like first trying to settle before entering a court of law. Saves money and time - just a better experience, even for the inocent user.

2 Likes

There are a lot of architecture problems that exist that did not translate well to the web, and yes some of them may have been questionable even before. A lot of this is however unfortunately academic.

3 Likes