This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,64 @@
--[[
Specialized analytics reporter for ephemeral counters.
Useful for tracking at-a-glance health of a feature.
]]
local UserInputService = game:GetService("UserInputService")
local Diag = {}
Diag.__index = Diag
-- reportingService - (object) any object that defines the same functions for Diag as AnalyticsService
function Diag.new(reportingService)
local rsType = type(reportingService)
assert(rsType == "table" or rsType == "userdata", "Unexpected value for reportingService")
local self = {
_reporter = reportingService,
_isEnabled = true,
}
setmetatable(self, Diag)
return self
end
-- isEnabled : (boolean)
function Diag:setEnabled(isEnabled)
assert(type(isEnabled) == "boolean", "Expected isEnabled to be a boolean")
self._isEnabled = isEnabled
end
-- counterName : (string) the name of the ephemeral counter to increment
-- amount : (int) the value to increment the counter by
function Diag:reportCounter(counterName, amount)
assert(type(counterName) == "string", "Expected counterName to be a string")
assert(type(amount) == "number", "Expected amount to be a number")
assert(self._isEnabled, "This reporting service is disabled")
-- use special naming convention for Xbox counters
-- the call to GetPlatform is wrapped in a pcall() because the Testing Service
-- executes the scripts in the wrong authorization level
local platformID = Enum.Platform.None
pcall(function()
platformID = UserInputService:GetPlatform()
end)
if platformID == Enum.Platform.XBoxOne then
counterName = "Xbox-" .. tostring(counterName)
end
-- the AnalyticsService should automatically handle batch reporting
self._reporter:ReportCounter(counterName, amount)
end
-- category : (string) the name of the statistics buffer which to append the data
-- value : (number) any type of numeric value
function Diag:reportStats(category, value)
assert(type(category) == "string", "Expected category to be a string")
assert(type(value) == "number", "Expected value to be a number")
assert(self._isEnabled, "This reporting service is disabled")
self._reporter:ReportStats(category, value)
end
return Diag
@@ -0,0 +1,176 @@
return function()
local Diag = require(script.Parent.Diag)
local testCounterName = "testCounter"
local testCounterAmount = 1
local testCategoryName = "testCategory"
local testCategoryValue = 98
local badTestCounterName = 5
local badTestCounterAmount = "hello"
local badTestCategoryName = {}
local badTestCategoryValue = {}
local DebugReportingService = {}
function DebugReportingService:ReportCounter(counterName, amount)
assert(counterName == testCounterName, "Unexpected value for counterName: " .. counterName)
assert(amount == testCounterAmount, "Unexpected value for amount: " .. amount)
end
function DebugReportingService:ReportStats(categoryName, value)
assert(categoryName == testCategoryName, "Unexpected value for category: " .. categoryName)
if value then
assert(value == testCategoryValue, "Unexpected value for value: " .. value)
end
end
describe("new()", function()
it("should construct with a Reporting Service", function()
local diag = Diag.new(DebugReportingService)
expect(diag).to.be.ok()
end)
it("should throw an error to be constructed without a Reporting Service", function()
expect(function()
Diag.new(nil)
end).to.throw()
end)
end)
describe("setEnabled()", function()
it("should succeed with valid input", function()
local diag = Diag.new(DebugReportingService)
diag:setEnabled(false)
diag:setEnabled(true)
end)
it("should disable the reporter", function()
local diag = Diag.new(DebugReportingService)
diag:setEnabled(false)
expect(function()
diag:reportCounter(testCounterName, testCounterAmount)
end).to.throw()
end)
end)
describe("reportCounter()", function()
it("should work when appropriately enabled / disabled", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:setEnabled(false)
diag:reportCounter(testCounterName, testCounterAmount)
end).to.throw()
diag:setEnabled(true)
diag:reportCounter(testCounterName, testCounterAmount)
end)
it("should succeed with valid input", function()
local diag = Diag.new(DebugReportingService)
diag:reportCounter(testCounterName, testCounterAmount)
end)
it("should throw an error with invalid input for the counter name", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(badTestCounterName, testCounterAmount)
end).to.throw()
end)
it("should throw an error with invalid input for the amount", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(testCounterName, badTestCounterAmount)
end).to.throw()
end)
it("should throw an error with completely invalid input", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(badTestCounterName, badTestCounterAmount)
end).to.throw()
end)
it("should throw an error if it is missing a counter name", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(nil, testCounterAmount)
end).to.throw()
end)
it("should throw an error if it is missing an amount", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(testCounterName, nil)
end).to.throw()
end)
it("should throw an error if it is missing any input", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportCounter(nil, nil)
end).to.throw()
end)
end)
describe("reportStats()", function()
it("should work when appropriately enabled / disabled", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:setEnabled(false)
diag:reportStats(testCategoryName, testCategoryValue)
end).to.throw()
diag:setEnabled(true)
diag:reportStats(testCategoryName, testCategoryValue)
end)
it("should succeed with valid input", function()
local diag = Diag.new(DebugReportingService)
diag:reportStats(testCategoryName, testCategoryValue)
end)
it("should throw an error with invalid input for the category name", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(badTestCategoryName, testCategoryValue)
end).to.throw()
end)
it("should throw an error with invalid input for the value", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(testCategoryName, badTestCategoryValue)
end).to.throw()
end)
it("should throw an error with completely invalid input", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(badTestCategoryName, badTestCategoryValue)
end).to.throw()
end)
it("should throw an error if it is missing a category name", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(nil, testCategoryValue)
end).to.throw()
end)
it("should throw an error if it is missing a value", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(testCategoryName, nil)
end).to.throw()
end)
it("should throw an error if it is missing any input", function()
local diag = Diag.new(DebugReportingService)
expect(function()
diag:reportStats(nil, nil)
end).to.throw()
end)
end)
end
@@ -0,0 +1,154 @@
--[[
Specialized reporter for RBX Event Ingest data.
Useful for tracking explicit user interactions with screens and guis.
]]
local UserInputService = game:GetService("UserInputService")
local function getPlatformTarget()
local platformTarget = "unknownLua"
local platformEnum = Enum.Platform.None
-- the call to GetPlatform is wrapped in a pcall() because the Testing Service
-- executes the scripts in the wrong authorization level
pcall(function()
platformEnum = UserInputService:GetPlatform()
end)
-- bucket the platform based on consumer platform
local isDesktopClient = (platformEnum == Enum.Platform.Windows) or (platformEnum == Enum.Platform.OSX)
local isMobileClient = (platformEnum == Enum.Platform.IOS) or (platformEnum == Enum.Platform.Android)
isMobileClient = isMobileClient or (platformEnum == Enum.Platform.UWP)
local isConsole = (platformEnum == Enum.Platform.XBox360) or (platformEnum == Enum.Platform.XBoxOne)
isConsole = isConsole or (platformEnum == Enum.Platform.PS3) or (platformEnum == Enum.Platform.PS4)
isConsole = isConsole or (platformEnum == Enum.Platform.WiiU)
-- assign a target based on the form factor
if isDesktopClient then
platformTarget = "client"
elseif isMobileClient then
platformTarget = "mobile"
elseif isConsole then
platformTarget = "console"
else
-- if we don't have a name for the form factor, report it here so that we can eventually track it down
platformTarget = platformTarget .. tostring(platformEnum)
end
return platformTarget
end
local EventStream = {}
EventStream.__index = EventStream
-- reportingService - (object) any object that defines the same functions for Event Stream as AnalyticsService
function EventStream.new(reportingService)
local rsType = type(reportingService)
assert(rsType == "table" or rsType == "userdata", "Unexpected value for reportingService")
local self = {
_reporter = reportingService,
_isEnabled = true,
}
setmetatable(self, EventStream)
return self
end
-- isEnabled : (boolean)
function EventStream:setEnabled(isEnabled)
assert(type(isEnabled) == "boolean", "Expected isEnabled to be a boolean")
self._isEnabled = isEnabled
end
-- eventContext : (string) the location or context in which the event is occurring.
-- eventName : (string) the name corresponding to the type of event to be reported. "screenLoaded" for example.
-- additionalArgs : (optional, map<string, Value>) table for additional information to appear in the event stream.
function EventStream:setRBXEvent(eventContext, eventName, additionalArgs)
local target = getPlatformTarget()
additionalArgs = additionalArgs or {}
assert(type(eventContext) == "string", "Expected eventContext to be a string")
assert(type(eventName) == "string", "Expected eventName to be a string")
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(self._isEnabled, "This reporting service is disabled")
-- This function fires reports to the server right away
self._reporter:SetRBXEvent(target, eventContext, eventName, additionalArgs)
end
-- eventContext : (string) the location or context in which the event is occurring.
-- eventName : (string) the name corresponding to the type of event to be reported. "screenLoaded" for example.
-- additionalArgs : (optional, map<string, Value>) map for extra keys to appear in the event stream.
function EventStream:setRBXEventStream(eventContext, eventName, additionalArgs)
local target = getPlatformTarget()
additionalArgs = additionalArgs or {}
assert(type(eventContext) == "string", "Expected eventContext to be a string")
assert(type(eventName) == "string", "Expected eventName to be a string")
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(self._isEnabled, "This reporting service is disabled")
-- this function sends reports to the server in batches, not real-time
self._reporter:SetRBXEventStream(target, eventContext, eventName, additionalArgs)
end
function EventStream:releaseRBXEventStream()
assert(self._isEnabled, "This reporting service is disabled")
self._reporter:ReleaseRBXEventStream(getPlatformTarget())
end
-- eventContext : (string) the location or context in which the event is occurring.
-- eventName : (string) the name corresponding to the type of event to be reported. "screenLoaded" for example.
-- additionalArgs : (optional, map<string, Value>) map for extra keys to appear in the event stream.
function EventStream:sendEventDeferred(eventContext, eventName, additionalArgs)
local target = getPlatformTarget()
additionalArgs = additionalArgs or {}
assert(type(eventContext) == "string", "Expected eventContext to be a string")
assert(type(eventName) == "string", "Expected eventName to be a string")
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(self._isEnabled, "This reporting service is disabled")
-- this function sends reports to the server in batches, not real-time
self._reporter:SendEventDeferred(target, eventContext, eventName, additionalArgs)
end
-- eventContext : (string) the location or context in which the event is occurring.
-- eventName : (string) the name corresponding to the type of event to be reported. "screenLoaded" for example.
-- additionalArgs : (optional, map<string, Value>) map for extra keys to appear in the event stream.
function EventStream:sendEventImmediately(eventContext, eventName, additionalArgs)
local target = getPlatformTarget()
additionalArgs = additionalArgs or {}
assert(type(eventContext) == "string", "Expected eventContext to be a string")
assert(type(eventName) == "string", "Expected eventName to be a string")
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(self._isEnabled, "This reporting service is disabled")
-- this function sends reports to the server in batches, not real-time
self._reporter:SendEventImmediately(target, eventContext, eventName, additionalArgs)
end
-- additionalArgs : (optional, map<string, string>) table for extra keys to appear in the event stream.
function EventStream:updateHeartbeatObject(additionalArgs)
additionalArgs = additionalArgs or {}
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(self._isEnabled, "This reporting service is disabled")
self._reporter:UpdateHeartbeatObject(additionalArgs)
end
return EventStream
@@ -0,0 +1,393 @@
return function()
local EventStream = require(script.Parent.EventStream)
local testArgs = {
testKey = "testValue"
}
local testContext = "testContext"
local testEvent = "testEventName"
local badTestArgs = "hello"
local badTestContext = {}
local badTestEvent = {}
local function isTableEqual(table1, table2)
if table1 == table2 then
return true
end
if type(table1) ~= "table" then
return false
end
if type(table2) ~= "table" then
return false
end
for key, _ in pairs(table1) do
if table1[key] ~= table2[key] then
return false
end
end
for key, _ in pairs(table2) do
if table2[key] ~= table1[key] then
return false
end
end
return true
end
local function createDebugReportingService()
local function validateInputs(eventTarget, eventContext, eventName, additionalArgs)
assert(eventTarget, "no value found for eventTarget")
assert(eventContext == testContext, "unexpected value for eventContext : " .. eventContext)
assert(eventName == testEvent, "unexpected value for eventName : " .. eventName)
if additionalArgs and not isTableEqual(additionalArgs, {}) then
assert(isTableEqual(additionalArgs, testArgs), "unexpected value for additionalArgs")
end
end
local DebugReportingService = {}
function DebugReportingService:SetRBXEvent(eventTarget, eventContext, eventName, additionalArgs)
validateInputs(eventTarget, eventContext, eventName, additionalArgs)
end
function DebugReportingService:SetRBXEventStream(eventTarget, eventContext, eventName, additionalArgs)
validateInputs(eventTarget, eventContext, eventName, additionalArgs)
end
function DebugReportingService:SendEventImmediately(eventTarget, eventContext, eventName, additionalArgs)
validateInputs(eventTarget, eventContext, eventName, additionalArgs)
end
function DebugReportingService:SendEventDeferred(eventTarget, eventContext, eventName, additionalArgs)
validateInputs(eventTarget, eventContext, eventName, additionalArgs)
end
function DebugReportingService:ReleaseRBXEventStream(eventTarget)
assert(eventTarget, "no value found for eventTarget")
end
function DebugReportingService:UpdateHeartbeatObject(additionalArgs)
if additionalArgs and not isTableEqual(additionalArgs, {}) then
assert(isTableEqual(additionalArgs, testArgs), "unexpected value for additionalArgs")
end
end
return DebugReportingService
end
describe("new()", function()
it("should construct with a Reporting Service", function()
local es = EventStream.new(createDebugReportingService())
expect(es).to.be.ok()
end)
it("should not allow construction without a Reporting Service", function()
expect(function()
EventStream.new(nil)
end).to.throw()
end)
end)
describe("setEnabled()", function()
it("should succeed with valid input", function()
local reporter = EventStream.new(createDebugReportingService())
reporter:setEnabled(false)
reporter:setEnabled(true)
end)
it("should disable the reporter", function()
local reporter = EventStream.new(createDebugReportingService())
reporter:setEnabled(false)
expect(function()
reporter:updateHeartbeatObject()
end).to.throw()
end)
end)
describe("setRBXEvent()", function()
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:setRBXEvent(testContext, testEvent, testArgs)
end)
it("should work when appropriately enabled / disabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:setRBXEvent(testContext, testEvent, testArgs)
end).to.throw()
es:setEnabled(true)
es:setRBXEvent(testContext, testEvent, testArgs)
end)
it("should throw an error if it is missing a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEvent(nil, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is missing an event name", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEvent(testContext, nil, testArgs)
end).to.throw()
end)
it("should succeed even if there aren't any additional args", function()
local es = EventStream.new(createDebugReportingService())
es:setRBXEvent(testContext, testEvent, nil)
end)
it("should throw an error if it is given bad input for a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEvent(badTestContext, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for a event", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEvent(testContext, badTestEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for additionalArgs", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEvent(testContext, testEvent, badTestArgs)
end).to.throw()
end)
end)
describe("setRBXEventStream()", function()
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:setRBXEventStream(testContext, testEvent, testArgs)
end)
it("should work when appropriately enabled / disabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:setRBXEventStream(testContext, testEvent, testArgs)
end).to.throw()
es:setEnabled(true)
es:setRBXEventStream(testContext, testEvent, testArgs)
end)
it("should throw an error if it is missing a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEventStream(nil, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is missing an event name", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEventStream(testContext, nil, testArgs)
end).to.throw()
end)
it("should succeed even if there aren't any additional args", function()
local es = EventStream.new(createDebugReportingService())
es:setRBXEventStream(testContext, testEvent, nil)
end)
it("should throw an error if it is given bad input for a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEventStream(badTestContext, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for a event", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEventStream(testContext, badTestEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for additionalArgs", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setRBXEventStream(testContext, testEvent, badTestArgs)
end).to.throw()
end)
end)
describe("sendEventImmediately()", function()
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:sendEventImmediately(testContext, testEvent, testArgs)
end)
it("should call without a fuss when enabled and throw when disabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:sendEventImmediately(testContext, testEvent, testArgs)
end).to.throw()
es:setEnabled(true)
es:sendEventImmediately(testContext, testEvent, testArgs)
end)
it("should throw an error if it is missing a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventImmediately(nil, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is missing an event name", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventImmediately(testContext, nil, testArgs)
end).to.throw()
end)
it("should succeed even if there aren't any additional args", function()
local es = EventStream.new(createDebugReportingService())
es:sendEventImmediately(testContext, testEvent, nil)
end)
it("should throw an error if it is given bad input for a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventImmediately(badTestContext, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for a event", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventImmediately(testContext, badTestEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for additionalArgs", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventImmediately(testContext, testEvent, badTestArgs)
end).to.throw()
end)
end)
describe("sendEventDeferred()", function()
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:sendEventDeferred(testContext, testEvent, testArgs)
end)
it("should call without a fuss when enabled and throw when disabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:sendEventDeferred(testContext, testEvent, testArgs)
end).to.throw()
es:setEnabled(true)
es:sendEventDeferred(testContext, testEvent, testArgs)
end)
it("should throw an error if it is missing a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventDeferred(nil, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is missing an event name", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventDeferred(testContext, nil, testArgs)
end).to.throw()
end)
it("should succeed even if there aren't any additional args", function()
local es = EventStream.new(createDebugReportingService())
es:sendEventDeferred(testContext, testEvent, nil)
end)
it("should throw an error if it is given bad input for a context", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventDeferred(badTestContext, testEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for a event", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventDeferred(testContext, badTestEvent, testArgs)
end).to.throw()
end)
it("should throw an error if it is given bad input for additionalArgs", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:sendEventDeferred(testContext, testEvent, badTestArgs)
end).to.throw()
end)
end)
describe("releaseRBXEventStream()", function()
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:releaseRBXEventStream()
end)
it("should throw when disabled and succeed when enabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:releaseRBXEventStream()
end).to.throw()
es:setEnabled(true)
es:releaseRBXEventStream()
end)
end)
describe("updateHeartbeatObject()", function()
it("should work when appropriately enabled / disabled", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:setEnabled(false)
es:updateHeartbeatObject(testArgs)
end).to.throw()
expect(function()
es:setEnabled(true)
es:updateHeartbeatObject(testArgs)
end).never.to.throw()
end)
it("should succeed with valid input", function()
local es = EventStream.new(createDebugReportingService())
es:updateHeartbeatObject(testArgs)
end)
it("should succeed even if there aren't any additional args", function()
local es = EventStream.new(createDebugReportingService())
es:updateHeartbeatObject(nil)
end)
it("should throw an error with invalid input", function()
local es = EventStream.new(createDebugReportingService())
expect(function()
es:updateHeartbeatObject(badTestArgs)
end).to.throw()
end)
end)
end
@@ -0,0 +1,51 @@
--[[
Specialized reporter for sending data to GA.
Useful for creating a breadcrumb trail of user interactions.
Events in GA are aggregated and organized in order by category, action, label.
]]
local GoogleAnalytics = {}
GoogleAnalytics.__index = GoogleAnalytics
-- reportingService : (table or userdata) any object that defines the same functions for GA as AnalyticsService
function GoogleAnalytics.new(reportingService)
local rsType = type(reportingService)
assert(rsType == "table" or rsType == "userdata", "Unexpected value for reportingService")
local self = {
_reporter = reportingService,
_isEnabled = true,
}
setmetatable(self, GoogleAnalytics)
return self
end
-- isEnabled : (boolean)
function GoogleAnalytics:setEnabled(isEnabled)
assert(type(isEnabled) == "boolean", "Expected isEnabled to be a boolean")
self._isEnabled = isEnabled
end
-- category : (string) the most generic category by which to organize data, ex) LuaApp, Errors, GameSettings, etc.
-- action : (string) a specific event to record, ex) ButtonPressed, GameExit
-- label : (string, optional) a detail to differentiate one action over another, ex) LoginButton, Exit Code 0
-- value : (integer, optional) the number of times this event has occurred
function GoogleAnalytics:trackEvent(category, action, label, value)
assert(type(category) == "string", "Expected category to be a string")
assert(type(action) == "string", "Expected action to be a string")
if label then
assert(type(label) == "string", "Expected label to be a string")
end
if value then
assert(type(value) == "number", "Expected value to be a number")
assert(value >= 0, "Expected value must not be a negative value")
end
assert(self._isEnabled, "This reporting service is disabled")
self._reporter:TrackEvent(category, action, label, value)
end
return GoogleAnalytics
@@ -0,0 +1,140 @@
return function()
local GoogleAnalytics = require(script.Parent.GoogleAnalytics)
local testCategory = "testCategory"
local testAction = "testAction"
local testLabel = "testLabel"
local testValue = 6
local badTestCategory = 13141
local badTestAction = {}
local badTestLabel = {}
local badTestValue = "heyo"
local DebugReportingService = {}
function DebugReportingService:TrackEvent(category, action, label, value)
if category ~= testCategory then
error("unexpected value for category: " .. category)
end
if action ~= testAction then
error("unexpected value for action: " .. action)
end
if label then
if label ~= testLabel then
error("unexpected value for label: " .. label)
end
end
if value then
if value ~= testValue then
error("unexpected value for value: " .. value)
end
end
end
describe("new()", function()
it("should construct with a Reporting Service and Logging Service", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(ga).to.be.ok()
end)
it("should fail to be constructed without a Reporting Service", function()
expect(function()
GoogleAnalytics.new(nil)
end).to.throw()
end)
end)
describe("setEnabled()", function()
it("should succeed with valid input", function()
local reporter = GoogleAnalytics.new(DebugReportingService)
reporter:setEnabled(false)
reporter:setEnabled(true)
end)
it("should disable the reporter", function()
local reporter = GoogleAnalytics.new(DebugReportingService)
reporter:setEnabled(false)
expect(function()
reporter:trackEvent(testCategory, testAction, testLabel, testValue)
end).to.throw()
end)
end)
describe("trackEvent()", function()
it("should work when appropriately enabled / disabled", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:setEnabled(false)
ga:trackEvent(testCategory, testAction, testLabel)
end).to.throw()
ga:setEnabled(true)
ga:trackEvent(testCategory, testAction, testLabel)
end)
it("should succeed with valid input", function()
local ga = GoogleAnalytics.new(DebugReportingService)
ga:trackEvent(testCategory, testAction, testLabel, testValue)
end)
it("should throw an error if it is missing a category", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(nil, testAction, testLabel, testValue)
end).to.throw()
end)
it("should throw an error if it is missing a testAction", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(testCategory, nil, testLabel, testValue)
end).to.throw()
end)
it("should not throw an error if it is missing a label", function()
local ga = GoogleAnalytics.new(DebugReportingService)
ga:trackEvent(testCategory, testAction, nil, testValue)
end)
it("should not throw an error if it is missing a value", function()
local ga = GoogleAnalytics.new(DebugReportingService)
ga:trackEvent(testCategory, testAction, testLabel)
end)
it("should throw an error if it is given invalid input for category", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(badTestCategory, testAction, testLabel, testValue)
end).to.throw()
end)
it("should throw an error if it is given invalid input for action", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(testCategory, badTestAction, testLabel, testValue)
end).to.throw()
end)
it("should throw an error if it is given invalid input for label", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(testCategory, testAction, badTestLabel, testValue)
end).to.throw()
end)
it("should throw an error if it is given invalid input for value", function()
local ga = GoogleAnalytics.new(DebugReportingService)
expect(function()
ga:trackEvent(testCategory, testAction, testLabel, badTestValue)
end).to.throw()
expect(function()
ga:trackEvent(testCategory, testAction, testLabel, -1)
end).to.throw()
end)
end)
end
@@ -0,0 +1,47 @@
--[[
Specialized reporter for sending data to InfluxDb.
Useful for very detailed information about specific errors.
Due to how Influx sends data, it is disallowed on XBox.
~Kyler Mulherin (9/12/2017)
]]
local Influx = {}
Influx.__index = Influx
-- reportingService - (object) any object that defines the same functions for Influx as AnalyticsService
function Influx.new(reportingService)
local rsType = type(reportingService)
assert(rsType == "table" or rsType == "userdata", "Unexpected value for reportingService")
local self = {
_reporter = reportingService,
_isEnabled = true,
}
setmetatable(self, Influx)
return self
end
-- isEnabled : (boolean)
function Influx:setEnabled(isEnabled)
assert(type(isEnabled) == "boolean", "Expected isEnabled to be a boolean")
self._isEnabled = isEnabled
end
-- seriesName : (string) the name of the series as it will appear in InfluxDb
-- additionalArgs : (map<string, string>) extra key/values to appear in each series
-- throttlingPercent : (int) the chance to actually report this series
function Influx:reportSeries(seriesName, additionalArgs, throttlingPercent)
additionalArgs = additionalArgs or {}
assert(type(seriesName) == "string", "Expected seriesName to be a string")
assert(type(additionalArgs) == "table", "Expected additionalArgs to be a table")
assert(type(throttlingPercent) == "number", "Expected throttlingPercent to be a number")
assert(throttlingPercent >= 0 and throttlingPercent <= 10000, "throttlingPercent must be between 0 - 10,000")
assert(self._isEnabled, "This reporting service is disabled")
self._reporter:ReportInfluxSeries(seriesName, additionalArgs, throttlingPercent)
end
return Influx
@@ -0,0 +1,165 @@
return function()
local Influx = require(script.Parent.Influx)
local testSeriesName = "testSeries"
local testArgs = {
testKey = "testValue"
}
local testThrottlingPercentage = 1000
local badTestSeriesName = 114
local badTestArgs = "someString"
local badThrottlingPercentage1 = -15
local badThrottlingPercentage2 = 150000
local badThrottlingPercentage3 = "a lot"
local function isTableEqual(table1, table2)
if table1 == table2 then
return true
end
if type(table2) ~= "table" then
return false
end
for key, _ in pairs(table1) do
if table1[key] ~= table2[key] then
return false
end
end
for key, _ in pairs(table2) do
if table2[key] ~= table1[key] then
return false
end
end
return true
end
local DebugReportingService = {}
function DebugReportingService:ReportInfluxSeries(seriesName, additionalArgs, throttlingPercentage)
if seriesName ~= seriesName then
error("Unexpected value for seriesName: " .. seriesName)
end
if throttlingPercentage ~= testThrottlingPercentage then
error("Unexpected value for throttlingPercentage: " .. throttlingPercentage)
end
if isTableEqual(additionalArgs, {}) == false then
if isTableEqual(additionalArgs, testArgs) == false then
error("Unexpected value for additionalArgs")
end
end
end
describe("new()", function()
it("should construct with a Reporting Service", function()
local influx = Influx.new(DebugReportingService)
expect(influx).to.be.ok()
end)
it("should fail to be constructed without a Reporting Service", function()
expect(function()
Influx.new(nil)
end).to.throw()
end)
end)
describe("setEnabled()", function()
it("should succeed with valid input", function()
local influx = Influx.new(DebugReportingService)
influx:setEnabled(false)
influx:setEnabled(true)
end)
it("should disable the reporter", function()
local influx = Influx.new(DebugReportingService)
influx:setEnabled(false)
expect(function()
influx:reportSeries(testSeriesName, testArgs, testThrottlingPercentage)
end).to.throw()
end)
end)
describe("reportSeries()", function()
it("should work when appropriately enabled / disabled", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:setEnabled(false)
influx:reportSeries(testSeriesName, testArgs, testThrottlingPercentage)
end).to.throw()
influx:setEnabled(true)
influx:reportSeries(testSeriesName, testArgs, testThrottlingPercentage)
end)
it("should succeed with valid input", function()
local influx = Influx.new(DebugReportingService)
influx:reportSeries(testSeriesName, testArgs, testThrottlingPercentage)
end)
it("should succeed even if it is missing any additionalArgs", function()
local influx = Influx.new(DebugReportingService)
influx:reportSeries(testSeriesName, nil, testThrottlingPercentage)
end)
it("should throw an error with invalid input for the seriesName", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:reportSeries(badTestSeriesName, testArgs, testThrottlingPercentage)
end).to.throw()
end)
it("should throw an error with invalid input for the throttlingPercentage - out of range - below zero", function()
expect(function()
local influx = Influx.new(DebugReportingService)
influx:reportSeries(testSeriesName, testArgs, badThrottlingPercentage1)
end).to.throw()
end)
it("should throw an error with invalid input for the throttlingPercentage - out of range - above cap", function()
expect(function()
local influx = Influx.new(DebugReportingService)
influx:reportSeries(testSeriesName, testArgs, badThrottlingPercentage2)
end).to.throw()
end)
it("should throw an error with invalid input for the throttlingPercentage - bad type", function()
expect(function()
local influx = Influx.new(DebugReportingService)
influx:reportSeries(testSeriesName, testArgs, badThrottlingPercentage3)
end).to.throw()
end)
it("should throw an error with completely invalid input", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:reportSeries(badTestSeriesName, badTestArgs, badThrottlingPercentage1)
end).to.throw()
end)
it("should throw an error if it is missing a seriesName", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:reportSeries(nil, testArgs, testThrottlingPercentage)
end).to.throw()
end)
it("should throw an error if it is missing a throttlingPercentage", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:reportSeries(testSeriesName, testArgs, nil)
end).to.throw()
end)
it("should throw an error if it is missing any input", function()
local influx = Influx.new(DebugReportingService)
expect(function()
influx:reportSeries(nil, nil, nil)
end).to.throw()
end)
end)
end