Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Querying a backend

Lens has two representations of the query in the search bar: the query store and the AST.

Query store

Once a user selects an element from the catalogue, it is added to the query store. The query store is of type QueryItem[][], where a QueryItem represents a chip in the search bar and the outer list represents a logical OR operation and the inner list a logical AND operation. Take for example this query:

Example of search bar

The query searches for patients with blood group A- and a body weight between 30 and 100 as well as patients with blood group B+ regardless of their body weight. In the query store this query is represented as follows:

[
    [
        {
            "id": "10254884-b969-4bb2-8da9-d40eeb08586e",
            "key": "blood-group",
            "name": "Blood group",
            "type": "EQUALS",
            "system": "",
            "values": [
                {
                    "name": "A+",
                    "value": "A+",
                    "queryBindId": "7003ba31-2523-4e38-ba56-adf54dbf05cb"
                }
            ]
        },
        {
            "id": "babb673e-43ee-4e9b-8d81-0b7f3d1e41d3",
            "key": "body_weight",
            "name": "Body weight",
            "type": "BETWEEN",
            "values": [
                {
                    "name": "30 - 100",
                    "value": {
                        "min": 30,
                        "max": 100
                    },
                    "queryBindId": "8ac6ad91-4f7a-4709-b701-eed7968ceb12"
                }
            ]
        }
    ],
    [
        {
            "id": "4fc693e6-7865-4075-a84f-66afab0db7a0",
            "key": "blood-group",
            "name": "Blood group",
            "type": "EQUALS",
            "system": "",
            "values": [
                {
                    "name": "B+",
                    "value": "B+",
                    "queryBindId": "250d0bc1-b39d-47b0-98d8-e59a06797fd2"
                }
            ]
        }
    ]
]

Applications can read and write the query store using the getQueryStore and setQueryStore functions.

AST

To allow external systems such as databases and APIs to understand the query, the internal query store is transformed into an AST (Abstract Syntax Tree). The AST is a tree structure with the AstTopLayer type for branches and the AstBottomLayerValue for leaves. Generally the AST structure allows arbitrarily nesting AND and OR operations, although the AST as generated by Lens always has an outer OR operation and an inner AND operation to reflect the struture of the query store. The aforementioned query looks as follows in AST form:

{
    "operand": "OR",
    "children": [
        {
            "operand": "AND",
            "children": [
                {
                    "key": "blood-group",
                    "operand": "OR",
                    "children": [
                        {
                            "key": "blood-group",
                            "type": "EQUALS",
                            "system": "",
                            "value": "A+"
                        }
                    ]
                },
                {
                    "key": "body_weight",
                    "operand": "OR",
                    "children": [
                        {
                            "key": "body_weight",
                            "type": "BETWEEN",
                            "system": "",
                            "value": {
                                "min": 30,
                                "max": 100
                            }
                        }
                    ]
                }
            ]
        },
        {
            "operand": "AND",
            "children": [
                {
                    "key": "blood-group",
                    "operand": "OR",
                    "children": [
                        {
                            "key": "blood-group",
                            "type": "EQUALS",
                            "system": "",
                            "value": "B+"
                        }
                    ]
                }
            ]
        }
    ]
}

Applications can get the AST from the search bar using the getAst function.

Querying a Focus instance

In the samply organization Focus is commonly used to parse the AST and execute the query. Because Focus only communicates over the Beam protocol, Spot is required as an intermediary. Applications can query Focus by listening for the lens-search-triggered event and sending the AST to the backend in the appropriate form:

import { getAst, clearSiteResults, querySpot } from "@samply/lens";

let abortController = new AbortController();
window.addEventListener("lens-search-triggered", () => {
    abortController.abort();
    abortController = new AbortController();
    clearSiteResults();

    const query = btoa(
        JSON.stringify({
            lang: "ast",
            payload: btoa(
                JSON.stringify({ ast: getAst(), id: crypto.randomUUID() }),
            ),
        }),
    );
    querySpot(backendUrl, siteList, query, abortController.signal, (result) => {
        // This is called once per site when its result is received.
    });
});

The querySpot function requires that you set the Spot URL in the Lens options:

"spotUrl": "https://locator-dev.bbmri-eric.eu/backend"

Usually Spot determines the list of sites to query via its SITES environment variable. You can optionally override the list of sites in the Lens options:

"sitesToQuery": ["lodz-test", "uppsala-test", "eric-test", "DNB-Test"]

Learn how to pass results to Lens in the Showing results guide.

Querying Focus with CQL

Some applications send CQL queries to Focus. In this case you need AST to CQL translation code in your application. You can get started by copying and adjusting the ast-to-cql-translator.ts, cqlquery-mappings.ts and measures.ts files from the CCP explorer repository. Sending the query would then look as follows:

import {
    getAst,
    clearSiteResults,
    buildLibrary,
    buildMeasure,
    querySpot,
} from "@samply/lens";

let abortController = new AbortController();
window.addEventListener("lens-search-triggered", () => {
    abortController.abort();
    abortController = new AbortController();

    // AST to CQL translation
    const cql = translateAstToCql(
        getAst(),
        false,
        "DKTK_STRAT_DEF_IN_INITIAL_POPULATION",
        measures,
    );
    const lib = buildLibrary(cql);
    const measure = buildMeasure(
        lib.url,
        measures.map((m) => m.measure),
    );

    clearSiteResults();
    const query = btoa(
        JSON.stringify({
            lang: "cql",
            lib,
            measure,
        }),
    );
    querySpot(backendUrl, siteList, query, abortController.signal, (result) => {
        // This is called once per site when its result is received.
    });
});