Converting a URL-encoded filter to a LABKEY.Filter - is there a quick and easy way?

LabKey Support Forum (Inactive)
Converting a URL-encoded filter to a LABKEY.Filter - is there a quick and easy way? Leo Dashevskiy  2015-04-27 15:33
Status: Closed
 
Hello, folks!


Is there a JavaScript API, which would allow conversion from a string-based filter description: "age%7Egt=0" (decoded would read "age~gt=0")


directly to an object, which is the result of the call:

LABKEY.Filter.create('age', 0, LABKEY.Filter.Types.GREATER_THAN)


And if there isn't one, it seems that it is needed, since you have 2 differnt approaches to filter type names.

On a related note: the R package Rlabkey's makeFilter() method seems to explicitly have such a mapping the other way and the class LABKEY.Filter.FilterDefinition seems to also have a method getURLSuffix(), which does the backward mapping, from what is needed.

Thanks.
-Leo
 
 
Jon (LabKey DevOps) responded:  2015-04-27 20:56
Hi Leo,

You're right. Looking at our API documentation, I'm not seeing anything that would convert a string into a filter. I can submit a feature request for you, but can you give me an example of how you expect this conversion to happen? Specifically, how should the API call look? Below is a basic example of what a regular filtered query looks like via the Javascript API. Can you provide an example of what your conversion string-based filter would ideally look like?

    LABKEY.Query.selectRows({
        schemaName: 'lists',
        queryName: 'People',
        success: onSuccess,
        failure: onFailure,
        filterArray: [
            LABKEY.Filter.create('FirstName', 'Johnny'),
            LABKEY.Filter.create('Age', 15, LABKEY.Filter.Types.LESS_THAN_OR_EQUAL)
            LABKEY.Filter.create('LastName', ['A', 'B'], LABKEY.Filter.Types.DOES_NOT_START_WITH)
        ]
    });

Regarding the Rlabkey package and the makeFilter operation order of "Column/Operator/Value", it looks like the Javascript API and Java API work as "Column/Value/Operator" while the Rlabkey, Perl, and Python APIs use the "Column/Operator/Value" order. If the APIs were to be changed, they would have to provide backward compatibility to allow existing queries to work.

Regards,

Jon
 
Leo Dashevskiy responded:  2015-04-28 08:37
A JS method that takes in an object, which has key value pairs in the form: "age~gt": 0, and returns the array of "filter objects", would be nice [ the input argument is the result of Ext.urlDecode( parametersString ), where parametersString is of the form blah&age%7Egt=0&blah... ]
Care must be taken to ensure that only pairs with keys containing a tilde "~" are converted, moreover, only those pairs, where the portion in a key after the tilde is a valid operator name, are converted.

What do you think?

Thanks, Jon.
 
Leo Dashevskiy responded:  2015-04-28 14:48
I meanwhile (it might be a while before the feature request gets realized) cooked myself up something like this:

            var
                filters = [],
                ar, cn, ft,
                map =
                    {
                        eq: 'EQUAL',
                        dateeq: 'DATE_EQUAL',
                        neq: 'NOT_EQUAL',
                        dateneq: 'DATE_NOT_EQUAL',
                        neqornull: 'NOT_EQUAL_OR_MISSING',
                        gt: 'GREATER_THAN',
                        dategt: 'DATE_GREATER_THAN',
                        gte: 'GREATER_THAN_OR_EQUAL',
                        dategte: 'DATE_GREATER_THAN_OR_EQUAL',
                        lt: 'LESS_THAN',
                        datelt: 'DATE_LESS_THAN',
                        lte: 'LESS_THAN_OR_EQUAL',
                        datelte: 'DATE_LESS_THAN_OR_EQUAL',
                        startswith: 'STARTS_WITH',
                        doesnotstartwith: 'DOES_NOT_START_WITH',
                        contains: 'CONTAINS',
                        doesnotcontain: 'DOES_NOT_CONTAIN',
                        containsoneof: 'CONTAINS_ONE_OF',
                        containsnoneof: 'CONTAINS_NONE_OF',
                        'in' : 'EQUALS_ONE_OF',
                        notin: 'EQUALS_NONE_OF',
                        between: 'BETWEEN',
                        notbetween: 'NOT_BETWEEN',
                        memberof: 'MEMBER_OF',
                        isblank: 'MISSING',
                        isnonblank: 'NOT_MISSING',
                        hasmvvalue: 'HAS_MISSING_VALUE',
                        nomvvalue: 'DOES_NOT_HAVE_MISSING_VALUE'
                    }
            ;


            $.each( params, function( k, v ){
                ar = k.split( '~' );
                if ( ar.length == 2 ){
                    
                    ft = LABKEY.Filter.Types[map[ar[1]]];
                    cn = ar[0];

                    if ( ft && cn.substring( 0, 6 ) == 'query.' ){
                        filters.push( LABKEY.Filter.create( cn.substring( 6 ), v, ft ) );
                    }
                }
            });

Where "params" is the input I wrote of and "filters" is the output I wrote of.

I got the backwards "map" from the makeFilter() R method (had to reverse it in R) and also clean up some to remove duplicates.
By the way, there are a few discrepancies in names between R's makeFilter() method and JS Filter.js file!

Oh, something I forgot in the previous post is that a URL encoding for a filter also starts with "query." and not the column name right away as I previously erroneously wrote, so I guess that's another requirement...

Thanks.
-Leo
 
Jon (LabKey DevOps) responded:  2015-05-06 12:48
Thanks Leo! I've submitted your idea to our developers for future consideration!

https://www.labkey.org/issues/home/Developer/issues/details.view?issueId=23247

Regards,

Jon
 
Leo Dashevskiy responded:  2015-10-20 13:24
We seem to have found a way around it by on the front side passing the string:

filters = Ext.encode( <QueryWebPart instance>.getDataRegion().getUserFilter() )

and on the back-end decoding it as follows:

filters <- RJSONIO::fromJSON( labkey.url.params$filters )

filter <- as.matrix( lapply( filters, function( e ){
    return( paste0( RCurl::curlEscape( e['fieldKey'] ), '~', e['op'], '=', RCurl::curlEscape( e['value'] ) ) );
}) );
if ( nrow( filter ) == 0 ){
  filter <- NULL;
}

The resulting object "filter" can then be passed directly as the value of the "colFilter" parameter of the "labkey.selectRows()" method, since it is in the exact same form as the result of a call to the "makeFilter()" method.