Foundry Analytics Foundations

Dorian Smiley
5 min readOct 12, 2024

--

Measuring Engagement and ROI for Palantir Foundry Applications

ROI

It’s shocking how few enterprise applications provide basic engagement metrics such as unique daily users, page views, etc. Even fewer perform value attribution for workflows and actions taken in applications. A truly minuscule number of enterprises calculate an ROI for their technology investments. It’s understandable, though, as you can not calculate ROI without value attribution, which you can not do without engagement metrics. Leaders who want to align IT investment with business value must take the first step and demand that all IT assets track engagement metrics and perform value attribution.

Engagement Metrics for Foundry Apps

Tracking engagement in Foundry applications can be tricky. You can not simply embed Google Analytics, and it is not advisable due to privacy concerns. Palantir does not (shockingly) offer a native analytics solution either. The most common low-code application-building tool in the Foundry suite, Workshop, does not expose a sandbox for executing JavaScript code or allow the loading of external libraries. This leaves developers with few options to enable analytics tracking events, which is why I built Foundry Analytics Foundations (FAF), a foundational ontology to surface your Foundry application engagement metrics.

Foundry Analytics Foundations ships with the following components:

  1. A tracking library built as a Slate module.
  2. A collector endpoint authored in TypeScript
  3. Customizable data transforms written in Python
  4. A KPI dashboard

Slate Module

Slate events trigger invocations of the collector endpoint, which allows running client-side JavaScript and access to globals like navigator. The ready event tracks page loads, while the getMessage handler receives interaction events from the parent Workshop applications. This module is embedded using an iFrame in workshop applications you want to instrument.

Slate Tracking Module

Collector Endpoint

The collector endpoint is a TypeScript function written in a code repository.

import { OntologyEditFunction, Edits, Timestamp, Integer } from "@foundry/functions-api";
import { Objects, Events } from "@foundry/ontology-api";

export class EventFunctions {
@Edits(Events)
@OntologyEditFunction()
public insertEvent(
eventName: string,
eventType: string,
application: string,
userId: string,
isAuthenticated: boolean,
userAgent: string,
timestamp?: Integer,
page?: string,
pageElement?: string,
anonymousId?: string,
userRole?: string,
sessionId?: string,
sessionStartTime?: Integer,
referrerUrl?: string,
referrerDomain?: string,
previousPage?: string,
deviceType?: string,
operatingSystem?: string,
browserName?: string,
browserVersion?: string,
screenResolution?: string,
platform?: string,
language?: string,
timezone?: string,
country?: string,
state?: string,
city?: string,
utmSource?: string,
utmMedium?: string,
utmCampaign?: string,
utmTerm?: string,
utmContent?: string,
contentId?: string,
contentType?: string,
contentCategory?: string,
abTestId?: string,
featureFlagId?: string,
experimentVariant?: string,
consentStatus?: string,
doNotTrack: boolean = true,
engagementTime: Integer = 0,
scrollDepthPercent: Integer = 0,
clickCount: Integer = 0,
): void {
// Generate a unique ID if not provided
const eventId = this.generateUUID();

// Create a new event object
const newEvent = Objects.create().events(eventId);

// Mandatory Fields
newEvent.timestamp = Timestamp.fromJsDate(new Date(timestamp || Date.now()));
newEvent.eventName = eventName;
newEvent.eventType = eventType;
newEvent.application = application;
newEvent.userId = userId;
newEvent.isAuthenticated = isAuthenticated;
newEvent.userAgent = userAgent;

// Optional Fields - Set only if provided
if (page) newEvent.page = page;
if (pageElement) newEvent.pageElement = pageElement;
if (anonymousId) newEvent.anonymousId = anonymousId;
if (userRole) newEvent.userRole = userRole;
if (sessionId) newEvent.sessionId = sessionId;
if (sessionStartTime) newEvent.sessionStartTime = Timestamp.fromJsDate(new Date(sessionStartTime));
if (referrerUrl) newEvent.referrerUrl = referrerUrl;
if (referrerDomain) newEvent.referrerDomain = referrerDomain;
if (previousPage) newEvent.previousPage = previousPage;
if (deviceType) newEvent.deviceType = deviceType;
if (operatingSystem) newEvent.operatingSystem = operatingSystem;
if (browserName) newEvent.browserName = browserName;
if (browserVersion) newEvent.browserVersion = browserVersion;
if (screenResolution) newEvent.screenResolution = screenResolution;
if (platform) newEvent.platform = platform;
if (language) newEvent.language = language;
if (timezone) newEvent.timezone = timezone;
if (country) newEvent.country = country;
if (state) newEvent.state = state;
if (city) newEvent.city = city;

// Campaign and Marketing Attribution
if (utmSource) newEvent.utmSource = utmSource;
if (utmMedium) newEvent.utmMedium = utmMedium;
if (utmCampaign) newEvent.utmCampaign = utmCampaign;
if (utmTerm) newEvent.utmTerm = utmTerm;
if (utmContent) newEvent.utmContent = utmContent;

// Engagement Metrics
if (engagementTime !== undefined) newEvent.engagementTime = engagementTime;
if (scrollDepthPercent !== undefined) newEvent.scrollDepthPercent = scrollDepthPercent;
if (clickCount !== undefined) newEvent.clickCount = clickCount;

// Content Interaction
if (contentId) newEvent.contentId = contentId;
if (contentType) newEvent.contentType = contentType;
if (contentCategory) newEvent.contentCategory = contentCategory;

// Feature Flags and Experiments
if (abTestId) newEvent.abTestId = abTestId;
if (featureFlagId) newEvent.featureFlagId = featureFlagId;
if (experimentVariant) newEvent.experimentVariant = experimentVariant;

// Compliance and Privacy
if (consentStatus) newEvent.consentStatus = consentStatus;
if (doNotTrack !== undefined) newEvent.doNotTrack = doNotTrack;

}

// Utility method to generate a UUID
private generateUUID(): string {
// Simple UUID generator (version 4 UUID)
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
/* eslint-disable */
const r = (Math.random() * 16) | 0;
const v = c == 'x' ? r : (r & 0x3) | 0x8;
/* eslint-enable */
return v.toString(16);
});
}
}

Data Transforms

FAF includes data transforms authored in a Python code repository. It uses the materialized view of the Events ontology object (populated by tracking events received via the collector endpoint) as its input and outputs the base KPIs as a time series. The event model is based loosely on heap.io and tries to avoid stuffing state into tracking events.

FAF Data Lineage

The App Analytics KPIs object contains the time series data created by the transform pipeline depicted below.

FAF Transfroms

Workshop Application

The base workshop application contains the base KPIs and serves as a template for building your analytics dashboard. Using object set filters, you can analyze multiple applications in a single dashboard.

Workshop Application Dashboard Edit View
Workshop Application Time Series Variables

Conclusion

Foundry Analytics Foundations (FAF) provides a robust foundation on which you can start measuring engagement and performing value attribution across your entire suite of Foundry applications. By ensuring Foundry applications accurately measure ROI for the business, you can shame alternative investments that do not do the same out of existence.

--

--

Dorian Smiley
Dorian Smiley

Written by Dorian Smiley

I’m an early to mid stage start up warrior with a passion for scaling great ideas. The great loves of my life are my wife, my daughter, and surfing!

No responses yet