With the Analytics Event Viewer tool described in the previous section, we expect developers to use this tool to observe and verify that the events related to their extensions (and the accompanying entity information) is accurate. In this section of the article, we would describe the different cases and methods to allow developers to fine tune the events and information captured to best fit the design and use case of respective extensions.
General guidelines on data collection
While we have no control on the data that is collected on extension level, it is important to note that the developer is responsible for ensure that:
- Data collected does not include any personal data as well as personal identifying information. For more clarification, see this link here What is considered personal data under the EU GDPR? - GDPR.eu
- Data collected must strictly belong to your manufacturer only. You should not collect any data that does not belong to your extension, for example, part numbers and prices of other snappers in the users’ drawing. If you are unsure what you intend to collect is data that only belongs to your manufacturer, please reach out to Developer Support for clarification.
- Data collected should not contain any product related information such as part numbers. Extension Analytics should focus on user behavior and user events, if you would like to track product related data, please check out Product Analytics instead
- Accurate data is collected for the field, for instance, a property intended for "snapper package" should only describe the package of the snapper class and nothing else. Garbage data will pollute the database and make it hard for users to query.
Overriding the "Entity key"
The entity key (or "statsEntityKey" as described in code) is the primary property of an event entity that developers can look to modify. This is only to be done for cases where the default entity key for said entity (e.g. a Snapper or a Window) does not meet any needed use cases on the Query page.
For example, the default entity key for a Snapper it's class name. If there's any case where two or more logical variations of a Snapper share the same class (hence same class name), a developer may use the "statsEntityKey" method in Snapper to reflect the variations. A developer may also simply want to use a different name as the key for a Snapper or Window class (that is not the class name itself).
The "statsEntityKey" exists as an overridable method in following classes:
- Snapper
- Window
Some guidelines for when overriding the default entity key.
- Do not perform any heavy computation in the "statsEntityKey" method as this is expected to be a fast-returning method that would be called frequently.
- Refer to information that is static or readily available very early in the object's lifecycle.
- Do not variate the "statsEntityKey" using states that are too dynamic (including timestamps, counters or highly variable options or dimensions). The query page is designed to provide insights by aggregating events of the same logical entity. If the entity key is too fragmented, it will serve minimal value for developers.
- Do not look to change the "statsEntityKey" often or frequently across releases. Query page insights provide the most value when data is available for a substantial period of time, hence changing the key often will limit the time period that a specific used key was valid for.
Adding missed events
For each of the intended user events, the system is able to capture those events by making the appropriate log function calls in core. This should capture those events for most cases. However, in any case, due to custom implementation, the particular user's action does not invoke the same point in core code, the event itself is missed by the system. For those cases, developers should choose to invoke the appropriate log functions in their extension code to achieve data completeness.
User Snapper insert events
Out of the box, the system uses the UndoChain to capture the insert events of the snappers. This applies to inserts via most Animations and other ways such as copy-paste. The following methods in UndoStep and UndoOp is responsible for captured the insert events (as G3UserEvent instances).
/** * UndoStep: Log G3UserEvents. */ extend public void logG3UserEvents() {} /** * UndoOp: Log G3UserEvents. */ extend public void collectG3UserEvents(G3UserEventEnv env);
If any custom implementation for users' insert of symbols that results in UndoInsertOp being added to the UndoChain would automatically have the events captured by the system. Else, if you are using a custom UndoOp, you would need to implement the above method to ensure the events gets captured into the G3UserEventEnv object.
Alternatively, you could also use the following interfaces to explicitly trigger a new Snapper insert event to capture.
// In: cm/core/eventLogFunctions.cm /** * Log Snapper Insert Event. */ public void logSnapperInsertEvent(Snapper snapper) { logSnapperEvent(cStatsActionSnapperInsert, snapper); } /** * Log Snapper Insert Event. */ public void logSnapperInsertEvent(Snapper snapper, int quantity) { logSnapperEvent(cStatsActionSnapperInsert, snapper, quantity); }
Dialog show/hide events
Dialog show/hide events are logged in core using the following interfaces. Search references to these interfaces in cm/win/FrameWindow.cm. If you have a case where a dialog show/hide event is not captured by the existing log points. you may call these interfaces yourselves.
public void logFrameWindowShowEvent(Window w)
public void logFrameWindowHideEvent(Window w)
Other events
To know where other events are logged from, find references to G3UserEvent in cm/core or cm/win and find their respective interface functions.
Verifying manufacturer association
Due to the manufacturer-exclusivity of the data as mentioned earlier, it is vital that for any event that is specific to an extension, portfolio or library, the respective identifiers are correctly captured. Only when these identifiers are valid, developers will be able to have access to these events when attempting to get insights via the query page.
For a given manufacturer specific event, a G3ManufacturerId object is defined, and it describes one of these following fields would need to be populated.
- Manufacturer package - describes the main package of an extension
- Manufacturer CID - describes the id (integer) of a catalogue portfolio
- Manufacturer CIL ID - describes the manufacturer id for CIL (Commercial Interiors Library) data.
The G3ManufacturerId state is observable via the Analytics Event Viewer tool for captured events.
For snapper event's, the following "statsManufacturer" value determines the manufacturer associated with the event. By default, it would derive the extension that owns the package that defines the snapper class.
/** * Stats specific manufacturer. * Snapper events require this to be defined, but a fallback exists. * Calculation part entries has this as a fallback if Part.statsManufacturer fails, * but can be omitted if not relevant. */ extend public G3ManufacturerId statsManufacturer() { symbol manPkg = class.pkg; manPkg = getMainExtensionPackage(manPkg) ?? manPkg; return G3ManufacturerId(manPkg); }
Similarly, a "statsManufacturer" method for Window classes for window related events (e.g. dialog show).
/** * Stats specific manufacturer. */ extend public G3ManufacturerId statsManufacturer() { return G3ManufacturerId(statsMainExtensionPackage(class.pkg)); }
Specifying entities for Data symbols
For any data-driven snapper events, the event would capture an entity that describes that Snapper and an entity that describes the data. For a typical DataSymbol
Snapper, despite the Snapper class is generic, the DataSymbol
data originates from a specific manufacturer's portfolio. Hence a developer would be able to query against Snapper events despite not having a custom class. For DataSymbol
Snappers or Snappers that extend from the DataSymbol
class, the entities required is captured automatically. DsDataStatsEntity
is a predefined entity class that will be recorded as part of any DataSymbol
Snapper events that are logged.
For extensions that implement their own data Snapper classes, there are interfaces available to supply the DsDataStatsEntity
for the appropriate events.
Shown in the snippet below, DsPData
has a built-in method that describes itself in the form of a DsDataStatsEntity
object.
/*********************************************************************** * Statistics ***********************************************************************/ /** * Entity to describe this Data symbol data for statistics. */ final public DsDataStatsEntity statsEntity() { DsDataStatsEntity entity(); entity.className = this.class.name; entity.classPkg = this.class.pkg; entity.enterpriseCode = enterprise; entity.vendorCode = vendor; entity.cid = dataCatalog ? dataCatalog.cid : -1; entity.prdCatVersion = prdCatVersion; entity.prdCatCode = prdCatCode; entity.partNumber = partNumber; entity.quantity = floorI(partQty); return entity; }
You may refer a customization example in FikaDataSnapper shown below, the statsEntity for the Data instance is invoked and supplied to the Snapper event data.
/*********************************************************************** * Statistics ***********************************************************************/ /** * Append auxiliary stats entities for this snapper event. * Append only known entity types. */ public void auxiliaryStatsEntities(G3StatsEntity[] entities, str action) { super(..); DsDataStatsEntity[] mDataEntities = mDataStatsEntities(); for (entity in mDataEntities) { entities << entity; } } /** * Return the data symbol data entity for this snapper event. */ extend public DsDataStatsEntity dataStatsEntity(str action) { if (DsPData data = getData()) { return data.statsEntity(); } return null; } /** * Return the data symbol multi data entities for this snapper event. */ extend public DsDataStatsEntity[] mDataStatsEntities() { if (DsMultiPData mData = getMultiData()) { DsDataStatsEntity[] mDataEntities(); for (data in mData.allDataSorted()) { mDataEntities << data.statsEntity(); } return mDataEntities; } return null; } /** * Stats specific manufacturer. * Optional per-part manufacturer (i.e. picklists). */ public G3ManufacturerId statsManufacturer() { if (DsPData data = getData()) { int cid = data.dataCatalog.?cid; return cid > 0 ? G3ManufacturerId(mCid=cid) : null; } return null; }
And it is recommended that you implement the "statsManufacturer" method and specify the "cid" of the data. Example shown above.
The entities captured are observable in the Analytics Event Viewer.
Custom events
Besides the pre-defined events supported on CET, the system allows for "custom" events that can be implemented in your respective extensions. Currently, two types of custom events are supported: Generic and Numeric.
Generic events
Generic events are similar to the pre-defined events on snappers or windows, where it serves to fulfil questions like "how long X has happened over the last two weeks?" or "how many times X has happened on Y in the past month?".
The following interface is the primary interface to be used to implement custom generic events.
//cm/win/customEventLogG3.cm /** * Log custom event. */ public void logCustomEvent(str action, str statsEntityKey, int quantity=1, symbol pkg=#:package : caller eval, bool lookupExtension=true)
Using the above interface, you can specify your "action" string, that typically describes what has happened and the "statsEntityKey" that typically what the event has happened on. However, you may choose to model your statistics use case and specific the most appropriate these values for each of these two fields.
Example: For Fika Office Panel Manager Dialog, a custom event was implemented to track whenever the user presses the "Refresh" button. For this case, "refresh" is chosen as the action, and "fika panel manager" is defined as the entity key.
//custom/fika/office/foDataPanelManagerDialog: refreshCB logCustomEvent("refresh", "fika panel manager");
You may also choose to include more than 1 GenericStatsEntity instances or entity instances of other entity types (such as SnapperStatsEntity or WindowStatsEntity) into the single custom event, using the interface below/
public void logCustomEvent(str action, G3StatsEntity[] entities, symbol pkg=#:package : caller eval, bool lookupExtension=true)
Numeric Events
Numeric events are to be used to track a specific numeric metric, which can be one of the following value types:
- Time:
- Example:
- Metric measured: time taken for a system validation to run
- Use case: To track how often users trigger the system validation and what's the average time taken.
- Example:
- Length/Area/Volume/Weight:
- Example:
- Metric measured: Width of table configured on animation
- Use case: To understand what most common table width is chosen by users and how often they deviate from the default.
- Example:
- Miscellaneous:
- This is to be used when the metric measured does not fit into any of the above value types.
- Example:
- Metric measured: Number of lights (accessory) configured on a product
- Use case: To understand what's the design tendencies of the users.
Note: for the different value types specified above, the value supplied to the interface needs to be converted to the respective SI unit for standardization.
This is the interface to be used to log custom numeric events:
//cm/win/customEventLogG3.cm /** * Log numeric event. */ public void logNumericEvent(str action, str numericEntityKey, double value, numericType numType, symbol pkg=#:package : caller eval, bool lookupExtension=true)
Example: For Fika Office Panel Manager Dialog, two numeric events were implemented:
- Time taken for the refresh operation to complete
- The width of the selected panel (when the refresh button in pressed)
//custom/fika/office/foDataPanelManagerDialog: refreshCB /** * Refresh callback. */ private void refreshCB(Control c) { if (OfficeSnapperManagerDialog dialog = c.parentFrame.OfficeSnapperManagerDialog) { double start = microTime(); foDataPanelManagerDialog.updateTypeDropDown(); foDataPanelManagerDialog.reselectPanelType(); double val = us(microTime() - start).secondsD; // small tests for analytics. logCustomEvent("refresh", "fika panel manager"); logNumericEvent("refresh time taken", "fika panel manager", val, numericType.time); if (Snapper panel = dialog.previewSnapper()) { // distance to force it to meters first. distance width = panel.localBound.w.distance; logNumericEvent("selected panel width", "fika panel manager", width, numericType.length); } } }
Button events
Note:
This portion of this article is applicable for CET version 15.5 onwards
Button press events are generically logged by the system in most standard cases. It's intended to be used to track and capture button presses regardless of how the button was implemented (i.e. may or may not be an object that derives from cm.win.Button class). Button click events have:
- "button_press" specified as the event action (See constant `cStatsActionButtonPress`)
- "ui" specified as the event category
- A WindowStatsEntity that describes the Button (or any Window object that the click event is triggered on)
- A UICallbackStatsEntity that describes the callback or "on press" event of the button press.
- contains a "callbackEntityKey" and "callbackEntityPkg"
- "manufacturer" of the event is specified using the owner of the package (callbackEntityPkg) specified in UICallbackStatsEntity
By default, for objects that derive from cm.win.Button class, button press event generates the UICallbackStatsEntity in the following manner:
- "callbackEntityPkg" is determined using the package of which the registered `pressCB` function is defined in.
- "callbackEntityKey" is determined using:
- "key" of the object, else if it's is null
- name of the registered "pressCB" function
For any non-standard cases, the statistics events for button press events can be configured and customized using the following interfaces below.
// cm.win.Control /** * Vector to set fields instead of overriding class. */ final public void statsSetCallbackIdentifiers(str action, symbol callbackPkg, str callbackKey)
The above method in cm.win.Control allows for the "callbackPkg" and "callbackKey" of the button press event (as part of its UICallbackStatsEntity) to be customized. Use "button_press" (via constant `cStatsActionButtonPress`) as the "action" argument.
// cm.win.Control /** * Callback source. * Eg: Click, double click, etc. */ extend public <symbol, str> callbackSource(str action)
If you have subclassed the Control or Button class, you may also choose to specify the package and key using the method above.
Lastly, for non-standard buttons, you may explicitly trigger your own call to log the button press event using the following function:
// cm.win, windowEventLog.cm /** * Log a button press event. * Button press can be click or release action. */ public void logButtonPressEvent(Control w, symbol callbackPkg, str callbackName)
Comments
0 comments
Please sign in to leave a comment.