Config Service

The configuration service evaluates a series of conditional configuration blocks and determines which blocks to keep in composing a final configuration document for the user interface. The configuration document consists of blocks that define JSON structures that are to be merged into a final JSON configuration that powers the user interface.

The configuration service executes two phases - evaluate and merge. It essentially looks like this:

[block1]   -->            --> [config1]  -->
[block2]   -->          
[block3]   -->  evaluate                      merge  --> [final configuration]
[block4]   -->            --> [config4]  -->
[block5]   -->            --> [config5]  -->

In the evaluate phase, the conditional blocks are evaluated to determine whether they should be kept. An evaluator is used and the current runtime state is matched against the given condition. If true, the block portion is carried forward.

In the merge phase, the carried forward blocks are merged into a final configuration document.

Each conditional block takes on a structure like this:

{
    "evaluator": "{evaluatorId}",
    "condition": {condition},
    "config": {
        ...
    }
}

The config section consists of any arbitrary JSON. Upon merge, the config section is merged together with any other config sections. The merge logic is pretty simple.

  • Objects are composed together key by key. Keys that already exist are overwritten by later config entries. null entries result in key removal.
  • Arrays are additive and append on merge
  • All other types are overwrite in nature. The last config entry with the key wins. null removes the key.

Once the merge completes, a final configuration JSON document is assembled. This document is for all intents and purposes normalized. The user interface looks to this document to tell it how to render and what to do. It defines things like:

  • What actions to include on the Document Actions page
  • What buttons to include in the Selection... dropdown when navigating documents
  • What menu options to show on the left-hand side when viewing a project
  • What dashlets to show on the platform dashlets page

All of these decisions take into account the context of what is being looked at (i.e. what URI is being looked at and what observables are populated), who is looking at it (i.e. who is logged in) and what their authorities and permissions are. Evaluators play an important role here. Each evaluator is essentially a small bit of very fast executing JavaScript that is responsible for making very quick decisions regarding the above.

The Config Service is built to be fast. It can consider a very large set of blocks and deduce a normalized configuration in a matter of milliseconds.

An Example

To get a sense of how this works, imagine we have three config blocks like this:

Block #1

{
    "evaluator": "document-is-file",
    "config": {
        "document-actions": [{
            "id": "custom-preview-in-website"
        }]
    }
}

Block #2

{
    "evaluator": "document-has-type",
    "condition": "my:article",
    "config": {
        "document-actions": [{
            "id": "custom-copy-text-to-clipboard"
        }]
    }
}

Block #3

{
    "evaluator": "and",
    "condition": [{
        "evaluator": "document-is-file"
    }, {
        "evaluator": "document-has-attachment",
        "condition": "scan"
    }]
    "config": {
        "document-actions": [{
            "id": "custom-download-scan"
        }]
    }
}

Each of these blocks provides conditions by which the document-actions array will be constructed. If all three were to evaluate to true, the resulting config document would look like this:

{
    "config": {
        "document-actions": [{
            "id": "custom-preview-in-website"
        }, {
            "id": "custom-copy-text-to-clipboard"
        }, {
            "id": "custom-download-scan"
        }]
    }
}

However, all three conditions will evaluate separately and will only be true depending on where the user is, who they are and what they're working with.

  • If the user were looking at a file, block #1 would be included since it has a document-is-file evaluator. Block #2 might be included if the document is of type my:article. And block #3 would only be included if the document were both a file and also had a scan attachment.

  • If the user were looking at a folder, only block #2 has a chance of being shown. And it would only show if the folder had the my:article node type. Which is possible, depending on how you model your content.

This is a contrived example but one which demonstrates how configuration blocks assemble based on runtime information about what is being viewed. In this case, the document-actions configuration changes. This configuration drives what menu options appear in the Cloud CMS user interface under the Actions dropdown for a file or a folder.

Replacing Blocks

In addition to the base properties of evaluator, condition and config, you can also use the replace setting to inform a block that it should override or completely replace any aggregated configuration that was composed prior to loading the current block.

In the example above, suppose that Block #3 had replace set high like this:

{
    "evaluator": "and",
    "condition": [{
        "evaluator": "document-is-file"
    }, {
        "evaluator": "document-has-attachment",
        "condition": "scan"
    }]
    "config": {
        "document-actions": [{
            "id": "custom-download-scan"
        }]
    },
    "replace": true
}

If Block #3 loads last, then it will completely overwrite Block #1 and Block #2. In a sense, Block #1 and Block #2 would be thrown out and Block #3 would be the only block loaded.

The replace true option is useful for overrides where overlaid configurations wish to completely tear down what was configured in the core and override with something entirely custom.

Note that the replace field is specified at the top level. It indicates that the entire configuration should be replaced.

Updating and Removing Sections

When multiple blocks are loaded for the same configuration space, they are merged together as per the example above. The merge process merges arrays and objects within the JSON. It is sensitive to ordering and allows for replacement if the replace boolean is used.

The key field places a special role for elements within the JSON config. When a key field is encountered, it is expected to uniquely identify the JSON sub-configuration.

For example, the core product ships with a default configuration that looks a bit like this...

{
    "config": {
        "blocks": [
            {
                "evaluator": "context-platform",
                "config": {
                    "context": {
                        "items": [
                            {
                                "key": "platform/dashboard",
                                ...
                            },
                            {
                                "key": "platform/resources",
                                ...
                            },
                            {
                                "key": "platform/collaborate",
                                ...
                            },
                            {
                                "key": "platform/developers",
                                ...
                            },
                            {
                                "key": "platform/manage",
                                ...
                            }                                                        
                        ]
                    }
                }
            }
        ]
    }
}

Where the key field identifies each of the configuration sections under the items array. The ... indicators simply serve to show where additional configuration would appear.

You can use this key field to include another configuration block that updates or merges in more configuration. For example, we could add a color field to the platform/developers section like this:

{
    "config": {
        "blocks": [
            {
                "evaluator": "context-platform",
                "config": {
                    "context": {
                        "items": [
                            {
                                "key": "platform/developers",
                                "color": "green"
                            }
                        ]
                    }
                }
            }
        ]
    }
}

This is a contrived example once again (setting color to green doesn't actually do anything) but it does demonstrate how you can inject that property onto an existing configuration. Other properties on the platform/developers will be unaffected.

Furthermore, you can remove sections by using the special remove field. Suppose we wanted to remove the platform/developers section altogether. We could do something like this:

{
    "config": {
        "blocks": [
            {
                "evaluator": "context-platform",
                "config": {
                    "context": {
                        "items": [
                            {
                                "key": "platform/developers",
                                "remove": true
                            }
                        ]
                    }
                }
            }
        ]
    }
}

This configuration block will cause the platform/developers element to be spliced out of (or removed from) the array. Other items in the array will not be affected.