Chapter 2 - Metadata Mojo

This tutorial is based on the metadata_mojo.js example, which can be found in the TRAC GitHub Repository under examples/apps/javascript.

It may be helpful to read the metadata model overview before starting this tutorial.

Searching

Metadata searches allow you to search the metadata database using tag attributes. Any and all tag attributes are searchable. A selection of standard search operators are available, which can be combined in logical expressions to form fine-grained searches. Any search can be executed “as-of” a previous point in time, to provide a consistent view of the historical state of the platform.

Find a single object

Starting with the simplest search, let’s look for an object based on a single tag attribute. In this example we’re looking for a particular type of schema using a single search term, schema_type = "customer_records".

Let’s create a request for this search:

27    const searchRequest = tracdap.api.MetadataSearchRequest.create({
28
29        tenant: "ACME_CORP",
30        searchParams: {
31
32            objectType: tracdap.ObjectType.SCHEMA,
33
34            search: {  term: {
35
36                attrName: "schema_type",
37                attrType: tracdap.STRING,
38                operator: tracdap.SearchOperator.EQ,
39                searchValue: { stringValue: "customer_records" }
40            }}
41        }
42    })

In the SearchParameters we set the type of object to search for, and provide a search expression containing a single search term.

To execute the search we use the search() method of the TracMetadataApi.

47export function findFirst(searchRequest) {
48
49    return metaApi.search(searchRequest).then(response => {
50
51        const nResults = response.searchResult.length;
52
53        if (nResults === 0)
54            throw new Error("No matching search results");
55
56        console.log(`Got ${nResults} search result(s), picking the first one`);
57
58        return response.searchResult[0].header;
59    });
60}

This method returns a search result, which is a list of tags without their definition bodies. In a real-world scenario, the list might be displayed in a search grid with the attributes as columns, so the user can look through the results and select the one they want. For this example we just select the first result.

We return the header field of the selected objet, which is a TagHeader object that can be used as an object ID. For example, this ID could be used in the Loading objects example in the previous tutorial, to load tag with its full definition.

Logical search expressions

Search terms can be combined to form logical expressions, where each expression is either a single term or a logical combination of other expressions.

Here is a simple example, starting with expression for the single term from the previous example:

62export function logicalSearch() {
63
64    const schemaTypeCriteria = tracdap.metadata.SearchExpression.create({
65        term: {
66            attrName: "schema_type",
67            attrType: tracdap.STRING,
68            operator: tracdap.SearchOperator.EQ,
69            searchValue: { stringValue: "customer_records" }
70        }
71    });

Now a second expression, we want to know if the business division attribute is one of the business divisions we are interested in:

71    });
72
73    const businessDivisionCriteria = tracdap.metadata.SearchExpression.create({
74        term: {
75            attrName: "business_division",
76            attrType: tracdap.STRING,
77            operator: tracdap.SearchOperator.IN,
78            searchValue: { arrayValue: {
79                items: [
80                    { stringValue: "WIDGET_SALES" },
81                    { stringValue: "WIDGET_SERVICES" },
82                    { stringValue: "WIDGET_RND_ACTIVITIES" }
83                ]
84            }}
85        }
86    });

See the documentation for SearchOperator for the list of all available search operators.

Now let’s create a logical expression, which combines the two previous expressions:

71    const logicalSearch = tracdap.metadata.SearchExpression.create({
72        logical: {
73            operator: tracdap.LogicalOperator.AND,
74            expr: [
75                schemaTypeCriteria,
76                businessDivisionCriteria
77            ]
78        }
79    });

Any number of expressions can be added to the AND clause without nesting, since AND is an associative operation. The OR operator works the same way. If you want to combine AND and OR operations then nesting is required (a single search term with the IN operator can often remove the need to use OR). For the logical NOT operator, list of expressions must contain exactly one expression (again, most search operators have negative equivalents which remove the need to use NOT).

Once the top level search expression is built, it can be included in a search request and used to call the search() method of the TracMetadataApi.

 98    const searchRequest = tracdap.api.MetadataSearchRequest.create({
 99
100        tenant: "ACME_CORP",
101        searchParams: {
102            objectType: tracdap.ObjectType.SCHEMA,
103            search: logicalSearch
104        }
105    });
106
107    return findFirst(searchRequest);
108}

More Mojo

Several other metadata features are available in the current release of TRAC, including:

  • The ability to update objects and retrieve the full history of object versions

  • Tags can be updated independently of their objects, with a full history of tag updates

  • Point-in-time searches and selectors, providing a historical snapshot across all of TRAC’s metadata

  • The metadata type system, which allows attributes to be defined with any supported data type

  • Multi-valued tag attributes

These features can be explored by looking at the documentation for the TracMetadataApi and the Metadata Listing.