DEV Community

Alexis Cazes
Alexis Cazes

Posted on

Generic Data Layer

This post is part of a series about auto-tagging

While using a tag management system (TMS) like Adobe Launch, you can use different tagging methodologies to achieve your requirements. There are 3 main concepts of tagging which are:

  • DOM scraping which uses the functionalities of the DOM API to gather the data from the web pages. While it is a fast and flexible approach, it is also fragile as any changes in the DOM can break your implementation without notice.
  • Direct Call Rule which involves calling the rule/tag directly from your platform code. This approach is less flexible than the DOM scraping one. It is more robust and allows you to streamline your implementation. One of the main issues with DCR implementation is that in most instances there is no defined data structure and it also requires you to use the reference to the TMS object in your platform source code which will become a huge technical debt.
  • Data Layer which allows you to define a JavaScript object which will contain a structured version of the data you need to collect. While it is in the same spirit as the DCR implementation, it has the benefits to not make any reference to the TMS object which removes the technical debt issue. You can also define a well structured object to meet all of your tagging needs.

What is a data layer?

A data layer is a data structure which allows you to categorize, capture and display the data in the most efficient way. As part of the tagging effort, you will be required to deploy analytics, personalization and marketing tags. In most cases each of these products will be provided by a different third party, and each of them will require the data to be passed in a specific format.

The data layer will allow you to expose client-side the details that will be required for the tagging implementation to work in the most efficient way.

It is stored in a JavaScript object that you can access at the window level. Each provider, may it be Google or Adobe, will have their own definition of the data layer. While it fits the requirements for the specific provider it will most likely not be flexible or portable for other providers. For this reason, you should define your own data layer definition.

Flat vs nested data layer

Data Layer structures come in different forms and shapes, some more complex than others. You can choose between two distinct types of data layers.

A flat data layer consists of a JavaScript object where each property is not an object. It can be a string or a number or a boolean or an array. Each properties are on the same level and when you access the data in your code, you just need to use window.myDataLayer.property1. While it seems that a flat data layer will be simpler to implement, it is in fact harder to maintain and consume. Unlike a nested data layer, you will need to keep in mind that each property needs to have a detailed naming.

i.e: for each page data entry point you will need to add page to the name of the property to understand that the detail is related to the page. So pageName, pageURL, pageQueryString.

A flat data layer does not have a concept of object-oriented programming so you cannot group data in data category type easily. It becomes harder to check the data layer state the more the data layer structure grows.

Unlike flat data layer, nested data layer is based on object-oriented programming. A property can be of any type and it is easier to group data in specific categories. It is also easier to define, maintain and extend a nested data layer. As you will define objects, it is therefore easier to add a new property to this object rather than figuring out if a property already exists to capture this data somewhere in the flat data layer.

//Flat
var digitalData = {
    pageName: "My page",
    pageUrl: "www.domain.com/something",
    pageQuery: "?param1=value",
    brand: "Brand1",
    userAuthenticated: true,
    userId: "111",
    userType: "Type1",
    siteSection: "Section1",
    siteBusinessArea: "Area1"
}

//Nested
var digitalData = {
    page: {
        name: "My Page",
        url: "www.domain.com/something",
        query: {
            string: "?param1=value",
            params: [{
                name: "param1",
                value: "value1"
            }]
        }
    },
    site: {
        brand: "Brand1",
        section: "Section1",
        business: {
            area: "Area1"
        }
    },
    user: {
        authenticated: true,
        id: 111,
        type: "Type1"
    }
}
Enter fullscreen mode Exit fullscreen mode

I will advise to always use a nested data layer. We want to be able to easily maintain and extend our data layer. We also want to easily inspect our data layer in the developer console. A nested data layer will allow you to navigate directly to the data type you want to validate instead of going through the list of all properties in the data layer object. So if you want to check all the details about page, then you simply need to navigate to this object in the data layer and you are sure you will not miss any data.

Array vs Object data layer root

For the nested data layers, you can either choose your data layer to be an array of objects or an object. If you have searched for data layer definition previously you would have seen both approaches. While both are viable you just need to consider which approach suits you best.

If you choose the array approach, you will have to push the persisting and the event data together each time an event/action happens. This means you will need to store the data somewhere so it can persist from one event to another one.

If you choose the root to be an object then you will need to make sure that each time a new action/event happens, all properties present in the object are either updated or removed as required. In this approach you can persist data directly in the data layer object and only remove them for specific logic.

i.e: if you have an application object, you need to make sure to remove it from your data layer if previous event was an APPLICATION COMPLETE. This means that new event should not have any application data present, failing to do so will inflate your application complete metrics.

You will also need to have a property that will be an array. This will be used as a notification layer so that you can easily watch for any changes in your data layer.

Define a generic data layer

Now that I have explained the different types of data layer, I will now explain how to achieve a generic definition of a data layer.

One of the main mistake in medium to large companies is the lack of a unique data layer definition across the company. In most cases each business area will operate in their own development cycle with their own development team. When you as a tagging member will request a data layer, they will fulfill your request but it is unlikely they will use the same property naming of same values for same outcome.

One simple example which I experienced was for the same platform but different product type, one cell put the applicationStatusCode as COMPLETED and another one as COMPLETE. In this instance, this meant that for one cell the implementation would have had application complete metrics in analytics while the other did not as COMPLETE was not supported in the tagging logic.

By defining a generic data layer definition across your company, it will allow you to achieve better data quality, efficient tagging and in the long run you can implement auto-tagging across the different business sections as same data structure will be expected for specific actions/events on the website.

Naming convention

Let’s define some data layer naming convention out of the box.

  • snake_case vs camelCase : I always prefer camelCase naming convention and that what we will use for the names of our properties.
  • do not repeat category name inside nested object. When using nested data later type it is not necessary to repeat the data category type in the object properties. For example, if you have an object as page, there is no need to use pageName inside the page property. Simply use name for the nested property and in your code using page.name is cleaner than using page.pageName as we already know we are navigating through the page object.
  • do not use leading underscore for property name
  • make property names as generic as possible, we want to reuse them across all your platforms. So do not name them related to the platform.

Use JSON schema definition to define your data layer

One of the main challenges that you will faces is to choose the right way to share the data layer definition across your company.

In my early attempts I used Confluence to document my data layer definition. While it worked initially it became soon really complex to maintain as I had to update multiple pages when adding a property (I used one page per object, so I needed to update the child then all of its parents each time).

I then stumbled upon JSON Schema Definition. This will allow you to define your data layer in a detailed and logical manner. You can provide the definition directly to your developer that should easily understand what is required, what are the limitations and which validations needs to be ran.

Our generic data layer

For our generic data layer, we will use a nested data layer. This data layer will have a root of an object. Each property can be of any type including an object. Each type of data should be defined as its own object. To future proof it we will make sure to create an object for each type of data category type even if it will contain one child property, this will allow us to easily extend it in the future.

We want to have a version property to keep track which version of our data layer is deployed on the platform. We also want to gather data about the page, site, server and user. As a notification layer we will use an events array. These properties would be our base data layer implementation and should be present in every single events.

var digitalData = {
    version: "",
    page: {},
    site: {},
    events: [],
    user: {},
    server: {}
}
Enter fullscreen mode Exit fullscreen mode
{
    "$schema": "https:/json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "version",
        "page",
        "site",
        "events",
        "user",
        "server"
    ],
    "properties": {
        "version": {},
        "page": {},
        "site": {},
        "events": {},
        "user": {},
        "server": {}
    }
}
Enter fullscreen mode Exit fullscreen mode

From now on I will define each required properties one by one. We will update the definition under the properties section of the JSON schema definition above. Each sub object will be defined in the same manner as the root one, we will define its type, what is required and the properties it may contain. We might also add enums which will list the only supported values and description so whoever reads this definition understand what this property should be.

version

The version property should always contain the latest version of our data layer. We will use vMajor.Minor.Patches notation. i.e: v1.0.0. Major should not be changed unless the release is breaking changes and will require a major refactoring. Minor should be incremented each time a new feature or main property is introduced like for example form which is not present in our current definition. And patches should be incremented each time you provide a fix to the definition.

So, for the version definition it will be as follow:

{
    "type": "string",
    "title": "Version of the generic data layer",
    "description": "The generic data layer is versioned to keep track of its changes, correct version needs to be specified",
    "enum": [
        "v1.0.0"
    ]
}
Enter fullscreen mode Exit fullscreen mode

server

The server property tracks the details about the platform that delivers the website. For the name you should put the code name that is used internally to identify the project or platform.

So, for server definition it will be as follow:

{
    "type": "object",
    "title": "Server details",
    "description": "Provide details of the current platform",
    "required": [
        "name"
    ],
    "properties": {
        "name": {
            "type": "string",
            "title": "Server name",
            "description": "Name of the platform serving the website, needs to be unique.",
            "examples": [
                "Project1",
                "ABC",
                "Platform 1"
            ]
        },
        "version": {
            "type": "string",
            "title": "Server version",
            "description": "Can either be the version number or the release code name"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

page

The page property will be used to track details about the current and previous page. We want to know its name, URL, query params, fragments and type. For type and name, agree with your developers to provide meaningful values.

So, for the page definition it will be as follow:

{
    "type": "object",
    "title": "Page details",
    "description": "Contains all details about current page loaded and previous page",
    "required": [
        "name",
        "url",
        "previous",
        "query",
        "fragments"
    ],
    "properties": {
        "name": {
            "type": "string",
            "title": "Page name",
            "description": "Page name will be reported as is. Needs to be unique"
        },
        "url": {
            "type": "string",
            "title": "Page URL",
            "description": "Full URL of the page loaded with query string parameters and fragments. Any sensitive data needs to be stripped out"
        },
        "previous": {
            "type": "object",
            "title": "Previous page details",
            "description": "Details of the previous page loaded",
            "required": [
                "name",
                "url"
            ],
            "properties": {
                "name": {
                    "type": "string",
                    "title": "Previous page name",
                    "description": "Previous page name will be reported as is. Needs to be unique"
                },
                "url": {
                    "type": "string",
                    "title": "Previous page url",
                    "description": "Full URL of the previous page loaded with query string parameters and fragments. Any sensitive data needs to be stripped out"
                }
            }
        },
        "query": {
            "type": "object",
            "title": "URL query string parameters details",
            "description": "Provide details of the query string present in the URL of the page loaded",
            "required": [
                "string",
                "parameters"
            ],
            "properties": {
                "string": {
                    "type": "string",
                    "title": "Full query parameters",
                    "description": "Should display the full query string parameters without the initial ? and without fragments"
                },
                "parameters": {
                    "type": "array",
                    "title": "Array of query string parameters",
                    "description": "This array should contain all query string parameters present in the URL of page loaded",
                    "items": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "title": "Query String param name",
                                "description": "Name of the query string param name"
                            },
                            "value": {
                                "type": "string",
                                "title": "Value of the query string param",
                                "description": "Value of the query string param"
                            }
                        }
                    }
                }
            }
        },
        "fragments": {
            "type": "string",
            "title": "URL fragments",
            "description": "Display the full URL fragments"
        },
        "type": {
            "type": "string",
            "title": "Page type",
            "description": "Provides the page type.",
            "examples": [
                "HOME",
                "PRODUCT",
                "SUPPORT"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

site

We also want to know the details about the site that we are on. Site data is generic to any type of implementation and can be easily reused.
We want to know which brand is being used. In some cases, some platforms use the same code but a different branding is displayed. However the underlying content and logic of the website is exactly the same.
It is also good to know how we reached the site. This is useful if you have a mobile app and a website. It can be that the user can access same content by being redirect from your app or from your website.
We also want to know which section of the site is being browsed.

So, for the site definition it will be as follow:

{
    "type": "object",
    "title": "Site details",
    "description": "Contains all the details about the website/platform loaded",
    "required": [
        "brand",
        "section",
        "channel",
        "business"
    ],
    "properties": {
        "brand": {
            "type": "string",
            "title": "Brand",
            "description": "The brand of the website"
        },
        "section": {
            "type": "string",
            "title": "Site section",
            "description": "Site section of the website",
            "examples": [
                "Section 1",
                "Sales"
            ]
        },
        "channel": {
            "type": "string",
            "title": "Site channel",
            "description": "Represent by which channel the customer reached the website",
            "enum": [
                "ONLINE",
                "MOBILE"
            ]
        },
        "business": {
            "type": "object",
            "title": "Site Business details",
            "description": "Contains all details about the website business section",
            "required": [
                "area"
            ],
            "properties": {
                "area": {
                    "type": "string",
                    "title": "Business area",
                    "description": "Represent the business area of the website",
                    "examples": [
                        "Personal",
                        "Business"
                    ]
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

user

We can gather some generic data about the user that browse your website. By default we can track if the user is logged in or logged out. You can also provide details about the user category and type.

So, for the user definition it will be as follow:

{
    "type": "object",
    "title": "User details",
    "description": "Provide all the details about the user of the website",
    "required": [
        "authenticated"
    ],
    "properties": {
        "authenticated": {
            "type": "boolean",
            "title": "User authenticated state",
            "description": "Should be set to true if user has logged in to the website"
        },
        "id": {
            "type": "string",
            "title": "User ID",
            "description": "ID of the user using the website, should be a non PII data"
        },
        "type": {
            "type": "string",
            "title": "User type",
            "description": "Type of the user",
            "examples": [
                "youth"
            ]
        },
        "segment": {
            "type": "string",
            "title": "User segment",
            "description": "If the user type is not enough to define the user, the segment should also be provided",
            "examples": [
                "minor",
                "adult"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

events

The events property will be used as a notification layer. This will be an array. It will be updated last when updating the data layer. By doing so we will notify that the data layer state has been fully updated due to a specific action/event.

As our data layer use an object as root, it means that it is possible for the state of the data layer to quickly change (i.e: page will change really quickly when a form is submitted from the FORM COMPLETE event to the next PAGE LOAD event.).

First let’s discuss about potential events/actions that may happen on your website:

  • we can have a PAGE event. For now, the default phase we want to notify for is LOAD.
  • we can have an APPLICATION event. The only phases we want to notify for are START and COMPLETE (either successfully or unsuccessfully). We will also define an application object later on to provide further details on the application state.
  • we can have a FORM event. The only phases we want to notify for are START, COMPLETE and FORM_FIELD_ACTION. FORM_FIELD_ACTION should be used when a field is interacted with, however if you have helper sections in your form like helper accordions then it is better to use the ACTION event. We will define a form object later on to provide more details about the form.
  • we can have an ACTION event. An action event is anything that we want to send a notification for but does not fall into any of the types above.

In terms of hierarchy:
PAGE
 ↳ APPLICATION
   ↳ FORM
     ↳ ACTION

This means that when a PAGE loads it is possible that there is also an APPLICATION START and FORM START. As it is also a PAGE LOAD then PAGE LOAD prevails. In this case we will only send one event PAGE LOAD and update the application and form property with correct phase.

Please notice that in this definition we introduce the use of allOf which allows us to define the logic and dependency between properties

So, for the events definition it will be as follow:

{
    "type": "array",
    "title": "Events array",
    "description": "Should not be reset but new events should be pushed.",
    "items": {
        "type": "object",
        "title": "Event",
        "description": "Event providing details of what happened on the page",
        "required": [
            "target",
            "phase",
            "track"
        ],
        "properties": {
            "pageSection": {
                "type": "string",
                "title": "Event page section",
                "description": "If the event is an interaction with an element then specify which section of the page was interacted with.",
                "examples": [
                    "hero",
                    "Top Navigation",
                    "Product details",
                    "Footer"
                ]
            },
            "target": {
                "type": "string",
                "title": "Event target",
                "description": "What generated this event, corresponding phase needs to be set",
                "enum": [
                    "PAGE",
                    "FORM",
                    "APPLICATION",
                    "ACTION"
                ]
            },
            "phase": {
                "type": "string",
                "title": "Event phase",
                "description": "For each specific target, a specific phase is available. See allOf definition."
            },
            "track": {
                "type": "boolean",
                "title": "Track event",
                "description": "Indicates if the event should be reported via analytics. Useful if some events should never be reported in Analytics."
            },
            "delay": {
                "type": "boolean",
                "title": "Event analytics delay",
                "description": "Indicates if the event should be delayed for analytics tracking"
            },
            "timeStamp": {
                "type": "number",
                "title": "Event timestamp",
                "description": "Timestamp of when the event happened."
            },
            "productId": {
                "type": "string",
                "title": "Event product ID",
                "description": "If the event is a result of an interaction with a product on the page then specify which product. i.e: Useful for checkout page to add to remove products."
            },
            "element": {
                "type": "object",
                "title": "Page element",
                "description": "Element on the page interacted with",
                "properties": {
                    "id": {
                        "type": "string",
                        "title": "Element DOM id",
                        "description": "Should be the id assigned to the element in the DOM"
                    },
                    "className": {
                        "type": "string",
                        "title": "Element DOM className",
                        "description": "Should be the className assigned to the element in the DOM"
                    },
                    "value": {
                        "type": "string",
                        "title": "Element DOM value",
                        "description": "Should be the value assigned to the element in the DOM. NO PII DATA SHOULD BE PRESENT"
                    },
                    "type": {
                        "type": "string",
                        "title": "Element DOM type",
                        "description": "Should be the type assigned to the element in the DOM"
                    },
                    "category": {
                        "type": "string",
                        "title": "Element category",
                        "description": "Element category"
                    },
                    "link": {
                        "type": "object",
                        "title": "Link details",
                        "description": "If the element interacted with is a link provide this property",
                        "properties": {
                            "destination": {
                                "type": "string",
                                "title": "Link destination",
                                "description": "Destination of the link",
                                "enum": [
                                    "External",
                                    "Internal",
                                    "On-page (no page reload)",
                                    ""
                                ]
                            },
                            "url": {
                                "type": "string",
                                "title": "Link URL",
                                "description": "Link URL"
                            }
                        }
                    }
                }
            }
        },
        "allOf": [
            {
                "if": {
                    "properties": {
                        "target": {
                            "const": "PAGE"
                        }
                    }
                },
                "then": {
                    "properties": {
                        "phase": {
                            "const": "LOAD"
                        }
                    }
                }
            },
            {
                "if": {
                    "properties": {
                        "target": {
                            "const": "FORM"
                        }
                    }
                },
                "then": {
                    "properties": {
                        "phase": {
                            "pattern": "^START|COMPLETE|FORM_FIELD_ACTION$"
                        }
                    }
                }
            },
            {
                "if": {
                    "properties": {
                        "target": {
                            "const": "APPLICATION"
                        }
                    }
                },
                "then": {
                    "properties": {
                        "phase": {
                            "pattern": "^START|COMPLETE$"
                        }
                    }
                }
            },
            {
                "if": {
                    "properties": {
                        "target": {
                            "const": "ACTION"
                        }
                    }
                },
                "then": {
                    "properties": {
                        "phase": {
                            "pattern": "^.*$"
                        }
                    }
                }
            }
        ],
        "dependencies": {
            "target": [
                "phase"
            ],
            "phase": [
                "target"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We are now ready to deploy the basic implementation of our data layer. The only events we should send right now are PAGE LOAD and ACTION. We have not defined the products, form and application property yet.

Products and data layer

It is likely that your website will use some sort of products on the page. While the page might not display the product per say, the page will be linked to a product.

i.e: During a credit card application, while each step in the application does not display the product, they are directly linked to the credit card product. We should therefore update the data layer accordingly to reference the credit card product for each step.

For our use case we will define the products property as an array of product objects. Each product will have an id, name, category id and category name.

For your implementation, check what is a proper definition of a product, which common criteria are shared across your products.

So, for the products definition it will be as follow:

{
    "type": "array",
    "title": "Products details array",
    "description": "Details of the products present on the page or interacted with or being applied for",
    "items": {
        "type": "object",
        "title": "Product detail",
        "description": "Details of the product",
        "required": [
            "id",
            "name",
            "category"
        ],
        "properties": {
            "id": {
                "type": "string",
                "title": "Product ID",
                "description": "Product ID",
                "examples": [
                    "id123",
                    "x1a2"
                ]
            },
            "name": {
                "type": "string",
                "title": "Product name",
                "description": "Name of the product as displayed for the customer",
                "examples": [
                    "Product 1",
                    "Product red"
                ]
            },
            "position": {
                "type": "number",
                "title": "Product position",
                "description": "Position of the product on the search result page. Starts from index 0."
            },
            "details": {
                "type": "array",
                "title": "Product details",
                "description": "Use this field to provide any additional details about the product",
                "items": {
                    "type": "string"
                }
            },
            "category": {
                "type": "object",
                "title": "Product category details",
                "description": "Product category details",
                "required": [
                    "name",
                    "code"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "title": "Product category name",
                        "description": "Name of the product category",
                        "examples": [
                            "Category 1"
                        ]
                    },
                    "code": {
                        "type": "string",
                        "title": "Product category code",
                        "description": "Internal Product category code",
                        "examples": [
                            "C1"
                        ]
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that we used position property here. While it is not required, it will have its specific use like when a customer can do a search and filter for specific products on your page. By analyzing the position of the specific product in your analytics tool, you might find out that if it is displayed in specific position range it is more likely to convert.

Forms and data layer

In this section we will introduce the form property. This will be used to track the interaction with a form on our website. The following form events will be tracked: START, COMPLETE and FORM_FIELD_ACTION.

In the events property we made a reference to the event type of FORM. This event type will be used for FORM_FIELD_ACTION event and COMPLETE event.

For the form START, it is possible that the form START will be part of the PAGE LOAD or the APPLICATION START in itself, therefore we will only update the form property when the form start is part of PAGE LOAD for example.

We also want to track the duration of the form. This will be a good indicator if your user struggles to complete it. Further analysis would be needed in this case to pinpoint the issue.

So, for the form definition it will be as follow:

{
    "type": "object",
    "title": "Form details",
    "description": "Provide details of the form the user is currently interacting with",
    "required": [
        "id",
        "name",
        "phase"
    ],
    "properties": {
        "id": {
            "type": "string",
            "title": "Form ID",
            "description": "ID of the form being interacted with"
        },
        "name": {
            "type": "string",
            "title": "Form common name",
            "description": "Common name of the form being interacted with"
        },
        "phase": {
            "type": "string",
            "title": "Form phase",
            "description": "Provide the details at which liefecycle phase of the form we are at.",
            "enum": [
                "START",
                "IN_PROGRESS",
                "COMPLETE"
            ]
        },
        "time": {
            "type": "object",
            "title": "Form time details",
            "description": "Provide details about the star time, end time and duration of the form interaction",
            "properties": {
                "startedAt": {
                    "type": "number",
                    "title": "Form started at",
                    "description": "Time in milliseconds of when the form started"
                },
                "completedAt": {
                    "type": "number",
                    "title": "Form completed at",
                    "description": "Time in milliseconds of when the form completed at"
                },
                "duration": {
                    "type": "number",
                    "title": "Form duration",
                    "description": "Duration in milliseconds it took the customer to complete the form"
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that for the phase we also have a value of IN_PROGRESS. The START phase should ever be set only once in the form property per form. In your implementation logic you should always switch the phase value to IN_PROGRESS if the previous value is START.

Application and data layer

If your website allows users to apply or quote for something, then the application property will allow you to describe this process. In most application we will have a START and an outcome may it be success or failure.

The application property is also the ideal candidate to be extended to provide further details about the product being applied to instead of the products object. For example, if your application is about a loan then you can extend your application object to contain a loan object with properties: amount, purpose, termMonths and termYears.

So, for the application definition it will be as follow:

{
    "type": "object",
    "title": "Application details",
    "description": "Provide all details about an application",
    "required": [
        "id",
        "name",
        "status",
        "time",
        "end",
        "start",
        "inProgress"
    ],
    "properties": {
        "id": {
            "type": "string",
            "title": "Application ID",
            "description": "ID of the application"
        },
        "name": {
            "type": "string",
            "title": "Application name",
            "description": "Common name of the application",
            "examples": [
                "My easy application"
            ]
        }
        "status": {
            "type": "object",
            "title": "Application Status details",
            "description": "Provide all the details of the application status",
            "required": [
                "code",
                "phase"
            ],
            "properties": {
                "decision": {
                    "type": "string",
                    "title": "Application decision",
                    "description": "Should be set at the end of the application",
                    "enum": [
                        "Approved",
                        "Referred",
                        "Declined",
                        "Error"
                    ]
                },
                "code": {
                    "type": "string",
                    "title": "Application status code",
                    "description": "Correspond to the status code set server side to provide details about the application status",
                    "examples": [
                        "IN_PROGRESS",
                        "COMPLETE",
                        "FRAUD_REFER"
                    ]
                },
                "phase": {
                    "type": "string",
                    "title": "Application status phase",
                    "description": "Should be set to the correct phase during the application lifecycle",
                    "enum": [
                        "START",
                        "COMPLETE",
                        "PENDING",
                        "DECLINE",
                        "EXIT",
                        "ERROR",
                        "IN_PROGRESS"
                    ]
                },
                "reasons": {
                    "type": "object",
                    "title": "Application status reasons details",
                    "description": "Provide more details on the application status phase chosen",
                    "properties": {
                        "error": {
                            "type": "string",
                            "title": "Application error reasons",
                            "description": "Reason on why application errored out"
                        },
                        "decline": {
                            "type": "string",
                            "title": "Application decline reasons",
                            "description": "Reason on why application was declined"
                        },
                        "exit": {
                            "type": "string",
                            "title": "Application exit reasons",
                            "description": "Reason on why application was exited"
                        },
                        "pending": {
                            "type": "array",
                            "title": "Application pending reasons",
                            "description": "Reason on why application was referred",
                            "items": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "time": {
            "type": "object",
            "title": "Application time details",
            "description": "Provide details about the duration of the application",
            "properties": {
                "startedAt": {
                    "type": "number",
                    "title": "Application started at",
                    "description": "Timestamp of when the application started"
                },
                "completedAt": {
                    "type": "number",
                    "title": "Application completed at",
                    "description": "Timestamp of when the application completed"
                },
                "duration": {
                    "type": "number",
                    "title": "Application duration",
                    "description": "Duration in milliseconds of the application"
                }
            }
        },
        "end": {
            "type": "boolean",
            "title": "Application end",
            "description": "Indicates if the application reached completion. Should be set to false if start is true or inProgress is true"
        },
        "start": {
            "type": "boolean",
            "title": "Application start",
            "description": "Indicates if the application just started. Should be set to false if end is true or inprogress is true"
        },
        "inProgress": {
            "type": "boolean",
            "title": "Application in-progress",
            "description": "Indicates if the application is inprogress. Should be set to false if start is true or end is true"
        },
        "quote": {
            "type": "object",
            "title": "Application quote details",
            "description": "All quote details",
            "properties": {
                "decision": {
                    "type": "string",
                    "title": "Application quote decision",
                    "description": "Decision of the application quote."
                },
                "phase": {
                    "type": "string",
                    "title": "Application quote phase",
                    "description": "Phase of the application quote. START: should be set when application.start is true. COMPLETE: should be set if application quote is successful. PENDING: Should be set if application quote has been referred or pended for any reasons. DECLINE: should be set if application quote has been declined for any reasons. EXIT: Should be set if application quote has been existed for any reasons. ERROR: Should be set if application quote has errored for any reasons",
                    "enum": [
                        "START",
                        "COMPLETE",
                        "PENDING",
                        "DECLINE",
                        "EXIT",
                        "ERROR",
                        "IN_PROGRESS"
                    ]
                },
                "code": {
                    "type": "string",
                    "title": "Application quote code",
                    "description": "Code of the application quote",
                    "examples": [
                        "ACCEPT",
                        "FRAUD_REFER"
                    ]
                }
            }
        }
    },
    "allOf": [
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "START"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": true
                    },
                    "end": {
                        "const": false
                    },
                    "inProgress": {
                        "const": false
                    },
                    "time": {
                        "startedAt": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "COMPLETE"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": true
                    },
                    "inProgress": {
                        "const": false
                    },
                    "status": {
                        "properties": {
                            "decision": {
                                "const": "Approved"
                            }
                        }
                    },
                    "time": {
                        "completedAt": {
                            "pattern": "[0-9]{1,}"
                        },
                        "duration": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "PENDING"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": true
                    },
                    "inProgress": {
                        "const": false
                    },
                    "status": {
                        "properties": {
                            "decision": {
                                "const": "Referred"
                            },
                            "reasons": {
                                "properties": {
                                    "pending": {
                                        "minItems": 1
                                    }
                                }
                            }
                        }
                    },
                    "time": {
                        "completedAt": {
                            "pattern": "[0-9]{1,}"
                        },
                        "duration": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "DECLINE"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": true
                    },
                    "inProgress": {
                        "const": false
                    },
                    "status": {
                        "properties": {
                            "decision": {
                                "const": "Declined"
                            },
                            "reasons": {
                                "properties": {
                                    "decline": {
                                        "pattern": "^.*$"
                                    }
                                }
                            }
                        }
                    },
                    "time": {
                        "completedAt": {
                            "pattern": "[0-9]{1,}"
                        },
                        "duration": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "ERROR"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": true
                    },
                    "inProgress": {
                        "const": false
                    },
                    "status": {
                        "properties": {
                            "decision": {
                                "const": "Error"
                            },
                            "reasons": {
                                "properties": {
                                    "error": {
                                        "pattern": "^.*$"
                                    }
                                }
                            }
                        }
                    },
                    "time": {
                        "completedAt": {
                            "pattern": "[0-9]{1,}"
                        },
                        "duration": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "EXIT"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": true
                    },
                    "inProgress": {
                        "const": false
                    },
                    "status": {
                        "properties": {
                            "reasons": {
                                "properties": {
                                    "exit": {
                                        "pattern": "^.*$"
                                    }
                                }
                            }
                        }
                    },
                    "time": {
                        "completedAt": {
                            "pattern": "[0-9]{1,}"
                        },
                        "duration": {
                            "pattern": "[0-9]{1,}"
                        }
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "status": {
                        "properties": {
                            "phase": {
                                "const": "IN_PROGRESS"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "start": {
                        "const": false
                    },
                    "end": {
                        "const": false
                    },
                    "inProgress": {
                        "const": true
                    }
                }
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Notice that an application can start on a page load, therefore it is not necessary to send a PAGE LOAD event followed by an APPLICATION START event. Only the PAGE LOAD event will be enough, and you should update the application properties to start accordingly.

Conclusion

For tagging implementation that is built around a data layer to be successful, it is advised to define a common/generic definition that will be reusable across all your platforms.
We want our data layer implementation to be maintainable, extendable, and reusable. To do so it is important that our data layer definition is generic enough.

Top comments (0)