@cuaatt/core
What is it?
Module @cuaatt/core
is main core module for composable user acceptance and telemetry tracking library. This library is using for collecting telemetry and user behaviour from page without big impact in application code. Telemetry can be composed of trigger event and have this logic on one place in your app. Module can be used separately, but it's included inside @cuaatt/react
library. So if you want to use react in your app, use @cuaatt/react
instead of this package.
Install
Do with npm:
npm install @cuaatt/core
How it works?
Telemetry is simple library that works on simple principles. There need to be defined attribute for zone (default "data-tl-zone"), action (default "data-tl-action") and optionally attribute for telemetry parameters (default "data-tl-params"). In telemetry settings you need to define on which events will be telemetry watching. All events defined here will be processed and from these events will be composed telemetry message that will be passed to handler.
Example of usage
<body>
<script type="text/javascript">
//some code to init telemetry here, normally will be written in ts and use import
</script>
<div data-tl-zone="main">
<div data-tl-zone="logo">
<img src="logo.png" alt="Logo" data-tl-action="logo-link" data-tl-params="[attr=alt]" />
</div>
<nav data-tl-zone="navigation">
<a href="#/home" title="Home" data-tl-action="navbar-link" data-tl-params="[attr=title][val:s:id=\"home\"]">Home</a>
<a href="#" title="Menu" onclick="openMenu();" data-tl-action="navbar-menu" data-tl-params="[attr=title][val:s:id=\"menu\"]">Menu</a>
<a href="#/about" title="About" data-tl-action="navbar-link" data-tl-params="[attr=title][val:s:id=\"about\"]">About</a>
<a href="#/contact" title="Contact" data-tl-action="navbar-link" data-tl-params="[attr=title][val:s:id=\"contact\"]">Contact</a>
</nav>
<div data-tl-zone="content">
This is content of content zone :)
</div>
<!-- This zone is added after clicked on menu button -->
<div data-tl-zone="menu">
<a href="#/app/1" title="Link 1" data-tl-action="menu-link" data-tl-params="[attr=title][val:s:id=\"link 1\"]">Link 1</a>
<a href="#/app/2" title="Link 2" data-tl-action="menu-link" data-tl-params="[attr=title][val:s:id=\"link 2\"]">Link 2</a>
</div>
</div>
</body>
Zone
Zones are part of page that are some logically different from another. For example different zones can be modal dialogs, some subpage or some floating menu. Basically it can be any element on page with zone attribute (default "data-tl-zone").
Example of usage for define zone
<div data-tl-zone="main">...</div>
Action
Actions are trigger by user and can be on buttons, links, spans and other elements. Action must contain attribute for action (default "data-tl-action").
Example of usage for define action
<button data-tl-action="action">...</button>
Parameters
For every zone and action there can be defined attribute for telemetry parameters (default "data-tl-params"). There is specific pattern that needs to be complied. More information about telemetry parameters pattern can be found in module @cuaatt/params
that is for attributes parsing and composing.
Example of usage for define action params
From this parameter, telemetry load element title and id attribute values, load also text of button and propagate valid: true into parameters in telemetry message. So spawned telemetry message for action "action" will have parameters object filled with properties: title, id, text and valid
.
<button title="Title" id="button1" data-tl-action="action" data-tl-params="[attr=title,id][text][val:b:valid=\"true\"]">Open menu</button>
Example of usage for define action params by composer
There is also helper package to compose attributes more friendly way. Module @cuaatt/params
is intended to do that. On example, you can see how easy is use composer. For better understanding, this example is in react.
import { compose } from "@cuaatt/params";
function comp() {
return (<button title="Title" id="button1" data-tl-action="action" data-tl-params={compose().attributes(["title", "id"]).text().values({ valud: true })}>Open menu</button>);
}
How to run demo?
- Download or clone this repo
- Use
npm install
oryarn install
- Use
npm run make-install
- Use
npm run preview
- Use generated url and open it in browser
How to initialize telemetry?
After successful installation, import telemetry package. There is default import and also types, that you will be needed. This package is very simple and basically exports only one initializer function and types.
init<Z, A, M, T>(handler, options?):
Telemetry<T, Z, A, M>
Telemetry initializer has 4 generic types, that will be used to help you with writing complex telemetry. There is list of all generic parameters and some description.
-
Z
- Define union type or enum with all supported zones. This zones will be available in composing telemetry rules. -
A
- Define union type or enum with all supported actions. These actions are also available in composing, and they are used as telemetry if is not specified. -
M
- Define union type or enum with all triggered telemetries. Telemetries are trigger when it's defined as direct, synthetic or network. For global resolve messages, there is usedA
instead ofM
. -
T
- List of all events that are watched by telemetry. By default, there are only these events"mouseup", "keyup", "click"
.
Parameters
-
handler:
TelemetryHandler<T, Z, A, M>
- Define callback function that returns telemetry messageTelemetryMessage<T, Z, A, M>
. In this message are all information that are necessary to determine user action. Also contains parameters, location and time. More info in special section. Handler can havedispose()
function, that is called after telemetry is destroyed. -
options:
(defaults:
TelemetryOptions<T, Z, A, A | M>
) =>
TelemetryOptions<T, Z, A, A | M>
- This is optional parameter that is used to configure telemetry. In case that you want to use complex telemetry, you must use options and setup rules for this. Options is provided as callback function that contains default settings as parameter and must return new settings.
Return type
-
Telemetry<T, Z, A, M>
- Return telemetry instance that provides function for getting current options and alsodispose
function for clean up and destroy.
Example of initialize
import init from "@cuaatt/core";
const Events = ["click"] as const; //list of all events that we want to watch
type Zone = "zone"; //list of zones that will be on app
type Action = "action"; //list of actions that will be on app
type Telemetry = Action | "telemetry"; //list of all telemetries that will be spawned and created by telemetry
const handler = (message) => {
//process messages
};
handler.dispose = () => { //dispose is optional
//come cleant up after teleemtry destroy
}
const tel = init<Zone, Action, Telemetry, typeof Events[number]>(
handler, //callback function fo handling telemetry messages
(defaultValues) => ({ ...defaultValues }) //options for telemetry
);
tel.dispose(); //destory and clear telemetry
Telemetry<T, Z, A, M>
This is telemetry object with current api.
dispose(): void;
This method is used to clean up whole telemetry, remove all handlers and watchers. After this call, telemetry will stop work and no event will be triggered.
options(): Required<
TelemetryOptions<T, Z, A, A | M>
>;
This method return settings of telemetry. This setting object is filled by default values, so you can get complete setting object event if you not provide it.
TelemetryOptions<T, Z, A, A | M>
events: Readonly<Array<T>>;
List of all events that are processed by telemetry. Default events, that are set up by telemetry are "mouseup", "keyup", "click"
. These events will be handled if you not provide this property. You are basically able to use every dom browser event that are known. All events that you provide here will be available in direct and synthetic rules settings.
Keep on mind, that the more events, the slower processing will be inside telemetry. Try to have as small events as possible.
zoneAttributes?: Readonly<Array<string>>;
List of all attributes, that will be used for reading zone name from it. By default, this value is set to "data-tl-zone"
. It's recommended to do not changed this setting and also not add more than one attribute that is used for zone info.
So why is there array of attributes instead one?
In old systems you can have zones defined by some special attribute and in every part of application can be another. For this case you can use more attributes for zone. But if you start a new app, use only one zone attribute!
actionAttributes?: Readonly<Array<string>>;
List of all attributes, that will be used for reading action name from it. By default, this value is set to "data-tl-action"
. It's recommended to do not changed this setting and also not add more than one attribute that is used for action info.
So why is there array of attributes instead one?
In old systems you can have actions defined by some special attribute and in every part of application can be another. For this case you can use more attributes for action. But if you start a new app, use only one action attribute!
paramsAttributes?: Readonly<Array<string>>;
List of all attributes, that will be used for reading params pattern from it. By default, this value is set to "data-tl-params"
. It's recommended to do not changed this setting and also not add more than one attribute that is used for params info.
So why is there array of attributes instead one?
In old systems you can have params defined by some special attribute and in every part of application can be another. For this case you can use more attributes for params. But if you start a new app, use only one params attribute!
direct?: Array<
DirectTelemetry<T, Z, A, A | M>
>;
List of all directs telemetries. These telemetries are called after user made some action. Can be defined exactly or with using regular expressions or special "*" character, that means everything. If you want to see properties of DirectTelemetry<T, Z, A, A | M>
, continue here. If you want to know how to compose telemetries more, continue to chapter How to compose telemetries.
network?: Array<
NetworkTelemetry<A | M>
>;
List of all network telemetries. These telemetries are called after network do some request. Can be defined exactly or with using regular expressions or special "*" character, that means everything. If you want to see properties of NetworkTelemetry<A | M>
, continue here. If you want to know how to compose telemetries more, continue to chapter How to compose telemetries.
synthetics?: Array<
SyntheticTelemetry<T, Z, A, A | M>
>;
List of all synthetic telemetries. These telemetries are called after some pair of direct or network telemetries occurred. If you want to see properties of SyntheticTelemetry<T, Z, A, A | M>
, continue here. If you want to know how to compose telemetries more, continue to chapter How to compose telemetries.
DirectTelemetry<T, Z, A, A | M>
This telemetry is called after user made some action. Can be defined exactly or with using regular expressions or special "*" character, that means everything. Direct telemetry has current list of properties.
type: T | RegExp | "*";
Can be on of type defined in events
property, or regular expression or anything ("*" character). This is used to say telemetry, what events can be handled for this direct telemetry.
action?: A | RegExp | "*";
Can be on of type defined in A
(action) types, or regular expression or anything ("*" character). This is used to say telemetry, what actions can be handled for this direct telemetry.
zone?: Z | RegExp | "*";
Can be on of type defined in Z
(zone) types, or regular expression or anything ("*" character). This is used to say telemetry, what zones can be handled for this direct telemetry.
creates?: M;
This property say what telemetry can be created after it match all types, actions and zones. This property can be optional and if not defined telemetry use same value as is in action
name in attribute.
Examples of direct telemetry definition
const tel = telemetry<Zone, Action, Telemetry, typeof Events[number]>(
handler,
(defaults) => {
return {
...defaults,
direct: [
//when "click" event with attr data-tl-action = "open-normal-link" in data-tl-zone = "zone"
{ type: "click", zone: "zone", action: "open-normal-link" },
//when "click" event with attr data-tl-action = "open-normal-link" in anything zone
{ type: "click", zone: "*", action: "open-normal-link" },
//when "click" event with attr data-tl-action = "open-external-link" in anything zone
{ type: "click", zone: "*", action: "open-external-link" },
//when "added" event for every zone creates telemetry "zone-appear"
{ type: "added", zone: "*", creates: "zone-appear" },
//when "removed" event for every zone creates telemetry "zone-disappear"
{ type: "removed", zone: "*", creates: "zone-disappear" },
//default settings (no need to write it, it's automatically added)
{ type: "*", zone: "*", action: "*" }
]
}
});
NetworkTelemetry<A | M>
This telemetry is called after network do some request. Can be defined exactly or with using regular expressions or special "*" character, that means everything. Network telemetry has current list of properties.
For now, telemetry only watch for calls through HttpXmlRequest or fetch API. Other call are not yet supported (for example web sockets).
method: NetworkMethods | Array<NetworkMethods> | "*";
Define what method was watched by telemetry. Can be network method, array of networks methods or "*" character for every method.
status: NetworkStatusCodes | Array<NetworkStatusCodes | RegExp> | RegExp | "*";
Defined which status codes was watched by telemetry. It's number, array numbers or regular expression or "*" character for every status code.
Keep on mind that there is status code 0 for request that are ends with some error before response come. More info about status code 0 is described on MDN web docs
url: string | RegExp | "*";
Define which url was watched by telemetry. Can by full url, regular expression or "*" character for every url.
creates: M;
This property say what telemetry can be created after it match network call. This property is required.
SyntheticTelemetry<T, Z, A, A | M>
This telemetry is called after some pair of direct or network telemetries occurred. In synthetic telemetry you must define start
and end
property. Based on this, telemetry create new message with telemetry defined in creates
property. Synthetic telemetry has current list of properties.(#how-to-compose-telemetries).
start:
TelemetryPoint<T, Z, A>;
Definition of start action. This is defined as TelemetryPoint<T, Z, A> and is required. When this action occurred, telemetry mark it as start for synthetic telemetry and will be waiting for end
action.
end:
TelemetryPoint<T, Z, A>;
Definition of end action. This is defined as TelemetryPoint<T, Z, A> and is required. When telemetry trigger start message and then trigger this end message, new telemetry based on start and end will be also created. In this case time start and end will be different, and you will be able to check duration.
creates: M;
This telemetry will be created after trigger start
and end
messages.
Examples of synthetic telemetry definition
const tel = telemetry<Zone, Action, Telemetry, typeof TelemetryEvents[number]>(
handler,
(defaults) => {
return {
...defaults,
synthetics: [
{
//start telemetry is when zone "zone" is added on page
start: { type: "added", zone: "zone" },
//end telemetry is when zone "zone" is removed from page
end: { type: "removed", zone: "zone" },
//after start and end occures, "zone-duration" telemtry will be spawned
creates: "zone-duration"
},
{
//start telemetry is when user "click" on action "open-external-link"
start: { type: "click", action: "open-external-link" },
//end telemetry is when request on server is done and not fail
end: { type: "network", action: "request-ok" },
//after start and end occures, "request-success-duration" telemtry will be spawned
creates: "request-success-duration"
},
{
//start telemetry is when user "click" on action "open-menu"
start: { type: "click", action: "open-menu" },
//end telemetry when zone "menu" is added on page
end: { type: "added", zone: "menu" },
//after start and end occures, "menu-opened-duration" telemtry will be spawned
creates: "menu-opened-duration"
}
]
}
});
TelemetryPoint<T, Z, A, A | M>
This telemetry point is used in synthetic telemetry.
type: T;
This is only required property, and it's used for define event type. In synthetic telemetry, properties must be fully specified, and you are not able to used regular expressions or "*" character.
action?: A;
This is optional property, and it's used for define action type. In synthetic telemetry, properties must be fully specified, and you are not able to used regular expressions or "*" character.
zone?: Z;
This is optional property, and it's used for define zone type. In synthetic telemetry, properties must be fully specified, and you are not able to used regular expressions or "*" character.
TelemetryMessage<T, Z, A, M>
This is object that is provided in telemetry callback function and contains all necessary info to send it on some telemetry server ot you can use already create tracker, that do everything for you.
time: { start: number, end: number }
This property contains start and end time of event. In most of the cases start and end are same. But if you have synthetic telemetry event, there can be different start and end time. You are able to calculate duration from it.
resolve:
TelemetryResolve
This property contains info about telemetry resolve status. More about resolving status is described in this chapter.
url: string
This property contains current url of browser, when telemetry create message.
Keep on mind that on synthetic telemetry there is always url of starting action, not ending action!
zones?: Array<Z>
All zones on path for telemetry. Zones can be empty or undefined for special types of telemetry. For network telemetry, zones are not defined at all.
Keep on mind that on synthetic telemetry there is always zones of starting action, not ending action!
action?: A | null;
Trigger or created action. Action can be null for special types of telemetry. For network telemetry, action is not defined at all.
Keep on mind that on synthetic telemetry there is always telemetry from
creates
property. If this property is not defined, action of starting action will be used!
telemetry?: M;
This is property that is get from creates
property. If this property is nod defined, action
property is used instead.
Keep on mind that on synthetic telemetry there is always telemetry from
creates
property that must be defined!
params?: Params | NetworkInfo;
This is object, that contains all parameters, that are defined by telemetry params pattern. About this pattern you can read examples and usages on separate module @cuaatt/params
. Telemetry automatically parse these data and creates params for you.
How to compose telemetries?
Composing telemetry in @cuaatt/core
is killer feature and it is a core of whole telemetry. Composing telemetry is not hard, but there are some rules that needs to be filled. As you already known from previous chapter there are 3 types of telemetries. Direct, Synthetic and Network. On start, you need define types for Zones and Actions and also define array of watched Events. These needs to be set up in TelemetryOptions. Then we can start define telemetries.
Direct telemetries
We can start with Direct telemetries. On next examples is shown how to set up rules for some most common cases and you will be able to compose your own telemetry in the future.
I want to track if user click on menu button in navigation zone.
On start, you don't need setup anything, because telemetry will compose message automatically, but this message will be resolved as TelemetryResolve.Global
. So if you want to be more specific, you can create direct telemetry than will be resolved sd TelemetryResolve.Full
.
direct: [ { type: "click", zone: "navigation", action: "menu-open", creates: "user-open-navigation-menu" } ]
I want to track if user click on link button in content zone.
Based on previous example you can do similar thing.
direct: [ { type: "click", zone: "content", action: "url-open", creates: "user-open-url-link" } ]
Ok. This telemetry will be work only in content zone and on url-open action. I will be resolved as TelemetryResolve.Full
. But this is work only for one zone. I don't want to specify this for every zone! So we can update this and make it available for every zone on page.
direct: [ { type: "click", zone: "*", action: "url-open", creates: "user-open-url-link" } ]
Ha super, now its work independently on zone and telemetry will be resolved as TelemetryResolve.Partial
. But hey we have another similar actions. Not only url-open but also document-open and image-open. We can change this pretty easy and make it working for this 3 actions work in every zone.
direct: [ { type: "click", zone: "*", action: /(url|document|image)-open/, creates: "user-open-url-link" } ]
Splendid! We use match anything character "*" and also regular expression to create really universal telemetry definition that will be resolved as TelemetryResolve.Partial
. So it's time to look on Network rules.
Network telemetries
Now we can look on network telemetries. These telemetries are used to trigger some telemetry messages based on network request.
I want to track if call on user login was successfully.
We want track is user make successfully login action on api. So we defined method, url and status code in telemetry and then telemetry which will be created after call. Keep on mind that this telemetry definition will be resolved as TelemetryResolve.Full
.
network: [ { method: "POST", url: "/api/login", status: 200, creates: "user-logged" } ]
I want to track errors on api calls.
Ok so now we have telemetry for login action. But i want to track all errors on api calls. This is good to track errors on page.
network: [ { method: "POST", url: /\/api\/[\s\S]*/g, status: [/^(4[0-9]{2})/, /^(5[0-9]{2})/, 0], creates: "api-call-fail" } ]
Good! Now every api POST call, that will be on url that starts with /api/ and fails (4xx, 5xx, 0 status codes) will be reported as a "api-call-fail" telemetry that will be resolved as TelemetryResolve.Partial
. This is handy right? You can cover all errors by one definition in telemetry! So now we can also change to track not only POST but all methods.
network: [ { method: "*", url: /\/api\/[\s\S]*/g, status: [/^(4[0-9]{2})/, /^(5[0-9]{2})/, 0], creates: "api-call-fail" } ]
Splendid! We have cover all and also this telemetry will be resolved as TelemetryResolve.Partial
. So it's time to look on Synthetic rules.
Synthetic telemetries
The last part is synthetic telemetry. Synthetic telemetry mean that you are able to compose some telemetry messages based on another telemetry messages. Due to this principple you are able to change what you want measure without big or none changes into your app. Look on the next example.
I want to check how long user stay in dialog with privacy policy.
synthetics: [ { start: { type: "added", zone: "app-privacy-policy" }, end: { type: "removed", zone: "app-privacy-policy" }, creates: "app-privacy-policy-read" } ]
So what's happened here? After zone "app-privacy-policy" appear on page (for example user click on button and dialog with privacy policy is opened), telemetry remember it and will be waiting for end telemetry message. After zone "app-privacy-policy" disappear from page, telemetry will create synthetic telemetry "app-privacy-policy-read". And this is all. From this message you are able to read how long user spend on privacy policy zone (dialog in this cas). But this is not all!
I want to check how long user waits for profile save.
network: [ { method: "POST", url: "/api/profile/", status: "*", creates: "profile-saved" } ]
synthetics: [ { start: { type: "click", action: "profile-save" }, end: { type: "network", action: "profile-saved" }, creates: "waits-on-profile-request" } ]
Cool! Now you have synthetic telemetry with direct and network telemetry! After user click on save button on profile page with action "profile-save", app creates POST request on server and after response is received, there will be spawned message "profile-saved". Immediately after that, telemetry creates synthetic message "waits-on-profile-request"! But this is still not all :) You are able to compose synthetic telemetries from another synthetic telemetries.
network: [
{ method: "POST", url: "/api/profile/", status: "*", creates: "profile-saved" },
{ method: "GET", url: "/api/home/", status: "*", creates: "home-refresh" },
]
synthetics: [
{ start: { type: "click", action: "profile-save" }, end: { type: "network", action: "profile-saved" }, creates: "waits-on-profile-request" },
{ start: { type: "synthetic", action: "waits-on-profile-request" }, end: { type: "network", action: "home-refresh" }, creates: "waits-on-profile-request-and-home-refresh" },
]
Yeah! And that's it. You are created synthetic telemetry that is composed of another synthetic telemetry! Sp now you are ready to start writing your own rules!
TelemetryResolve
and what is it?
When telemetry resolving actions, zones and network messages, it tries to prioritize most specific message. There is example of direct telemetries, but it is same for others.
direct: [
//1. this is global action in main zone, catch any action in main zone
{ type: "click", zone: "main", action: "*" },
//2. this is not global action but can catch more actions with given regular expression
{ type: "click", zone: "main", action: /open-[a-zA-Z]{0,}-link/g },
//3. this is specific action because cathc only action with "open-normal-link"
{ type: "click", zone: "main", action: "open-normal-link" },
]
When user click for example on link <div data-tl-zone="main"><a href="#link" data-tl-action="link">Link</a></div>
, telemetry try to found corresponding definition. Type 3. is not matched and type 2. also not because is not match regular expression. The last matched is 1. which is matched because action is defined as "*". Telemetry spawn message for direct telemetry 1. with action "link". Because this is a telemetry with "*" character, this message will resolve as TelemetryResolve.Global
When user click for example on link <div data-tl-zone="main"><a href="#link" data-tl-action="open-external-link">Link</a></div>
, telemetry found that matched for this action is 1. and 2. direct telemetry. As is described in previous paragraph, telemetry 1. is resolved as TelemetryResolve.Global
, but telemetry 2. is resolve as TelemetryResolve.Parial
, because there is regular expression. And because telemetry 2. us more precise, will be spawned as telemetry message.
When user click for example on link <div data-tl-zone="main"><a href="#link" data-tl-action="open-normal-link">Link</a></div>
, telemetry found that matched for this action is 1., 2. nad 3. direct telemetry. And because 3. telemetry rule has no regular expression and "*" character, will be resolved as TelemetryResolve.Full
. And also is most specific and will be spawned as telemetry message.
In case that there are more exact matched telemetries, the first one in order inside settings will be spawned. But try to not have more than one
TelemetryResolve.Full
resolve telemetry.
Trackers
There is list of already implemented trackers that can be simple used with @cuaatt/core
telemetry.
- Google Analytics - tracker for usage with Google Analytics service.
😉
Donate me QR | Paypal |
---|---|
License
MIT - MIT License