internal void Transition(UserflowState newState) { // Transition a userflow from current state to newState . if (newState == UserflowState.CANCELLED) { SetState(newState, DateTime.UtcNow.Ticks); } else { switch (state) { case UserflowState.CREATED: if (newState == UserflowState.BEGUN) { SetState(newState, DateTime.UtcNow.Ticks); } else if (newState == UserflowState.CRASHED) { // NOP. Leave userflow in CREATED state. } else { // Userflow being begun for the first time after create. // Crittercism spec says newState has to be // UserflowState.BEGUN in this case unless there is change // of opinion about immediately failing a userflow possibility. DebugUtils.LOG_ERROR("Ending userflow that hasn't begun is forbidden."); } break; case UserflowState.BEGUN: if (newState != UserflowState.BEGUN) { SetState(newState, DateTime.UtcNow.Ticks); } else { // Complain. Crittercism spec says you shouldn't begin userflow // more than once. DebugUtils.LOG_ERROR("Beginning userflow more than once is forbidden."); } break; default: if (newState != UserflowState.TIMEOUT) { // Already in final state. We are only checking for TIMEOUT to prevent // printing this message (the Userflow must have entered some final // state in the nick of time). DebugUtils.LOG_ERROR("Ending userflow more than once is forbidden."); } break; } } }
public void TestTransition() { { // Resulting state are correct. UserflowState state1 = UserflowState.BEGUN; Userflow example = ExampleUserflow(); example.Transition(state1); Assert.IsTrue(example.State() == state1, "Confirm Transition changes state to given state"); } }
public void TestSaveLoad() { // Load saved userflow. Does it look the same? // Extract fields from userflow before saving. Userflow example1 = ExampleUserflow(); string firstName = example1.Name(); UserflowState firstState = example1.State(); long firstTimeout = example1.Timeout(); int firstValue = example1.Value(); long firstEyeTime = example1.EyeTime(); string firstBeginTime = example1.BeginTimeString(); string firstEndTime = example1.EndTimeString(); // Save followed by load. UserflowReporter.Background(); UserflowReporter.Foreground(); example1 = Userflow.UserflowForName(firstName); Assert.IsNotNull(example1, "Expecting to find example1 again"); // Extract fields from loaded userflow. string secondName = example1.Name(); UserflowState secondState = example1.State(); long secondTimeout = example1.Timeout(); int secondValue = example1.Value(); long secondEyeTime = example1.EyeTime(); string secondBeginTime = example1.BeginTimeString(); string secondEndTime = example1.EndTimeString(); // Everything is supposed to match now (within limits of persisting doubles). Assert.IsTrue(firstState == secondState, "Expecting firstState==secondState"); Assert.IsTrue(firstName == secondName, "Expecting firstName==secondName"); Assert.IsTrue(firstTimeout == secondTimeout, "Expecting firstTimeout==secondTimeout"); Assert.IsTrue(firstValue == secondValue, "Expecting firstValue==secondValue"); Assert.IsTrue(firstEyeTime == secondEyeTime, "Expecting firstEyeTime==secondEyeTime"); Assert.IsTrue(firstBeginTime == secondBeginTime, "Expecting firstBeginTime==secondBeginTime"); Assert.IsTrue(firstEndTime == secondEndTime, "Expecting firstEndTime==secondEndTime"); //UserflowReporter.Background(); Trace.WriteLine("testSaveLoad EXITING"); Trace.WriteLine(""); }
internal Userflow(string name, long beginTime, long endTime) : this(name, 0) { //////////////////////////////////////////////////////////////// // Input: // name = userflow name // beginTime = userflow begin time in ticks // endTime = userflow end time in ticks // NOTE: Automatic userflows ("App Load", "App Foreground", "App Background") //////////////////////////////////////////////////////////////// state = UserflowState.ENDED; SetBeginTime(beginTime); SetEndTime(endTime); eyeTime = endTime - beginTime; SetForegroundTime(beginTime); // This "Save" needs to occur after the "state" assigned above is known. Debug.WriteLine("Reporting '" + name + "' == " + (eyeTime / (double)TimeUtils.TICKS_PER_SEC) + " seconds"); UserflowReporter.Save(this); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { Userflow userflow = null; // Load JArray from stream . For better or worse, probably a bit of the latter, // Newtonsoft.Json deserializes a persisted timestamp string as a JTokenType.Date . JArray a = JArray.Load(reader); if (IsUserflowJson(a)) { // Extract values from "JArray a" . This is all according to the // Crittercism "Userflows Wire Protocol - v1" in Confluence. string name = (string)((JValue)(a[(int)UserflowIndex.Name])).Value; UserflowState state = (UserflowState)(long)((JValue)(a[(int)UserflowIndex.State])).Value; double timeoutSeconds = Convert.ToDouble(((JValue)(a[(int)UserflowIndex.Timeout])).Value); // seconds (!!!) int timeout = (int)Convert.ToDouble(timeoutSeconds * TimeUtils.MSEC_PER_SEC); // milliseconds int value = Convert.ToInt32(((JValue)(a[(int)UserflowIndex.Value])).Value); #if DEBUG { // NOTE: Userflow metadata skeleton in the closet. Tossed around // in early design and even implemented in iOS SDK client side. It // was never supported on platform nor publicly exposed to users. JObject o = a[(int)UserflowIndex.Metadata] as JObject; Debug.Assert(o.Count == 0); } #endif Dictionary <string, string> metadata = new Dictionary <string, string>(); long beginTime = JsonUtils.JsonDateToTicks(a[(int)UserflowIndex.BeginTime]); // ticks long endTime = JsonUtils.JsonDateToTicks(a[(int)UserflowIndex.EndTime]); // ticks double eyeTimeSeconds = Convert.ToDouble(((JValue)(a[(int)UserflowIndex.EyeTime])).Value); // seconds (!!!) long eyeTime = (long)(eyeTimeSeconds * TimeUtils.TICKS_PER_SEC); // ticks // Call Userflow constructor. userflow = new Userflow( name, state, timeout, value, metadata, beginTime, endTime, eyeTime ); } return(userflow); }
private Userflow() { name = ""; state = UserflowState.CREATED; value = NULL_VALUE; metadata = new Dictionary <string, string>(); // 0 ticks corresponds to reference date of // 12:00:00 midnight, January 1, 0001 // (0:00:00 UTC on January 1, 0001, in the Gregorian calendar) // https://msdn.microsoft.com/en-us/library/system.datetime.ticks(v=vs.110).aspx // And we are calling SetBeginTime and SetEndTime here so the strings // beginTimeString and endTimeString will get computed. const long referenceTime = 0; SetBeginTime(referenceTime); SetEndTime(referenceTime); eyeTime = 0; SetForegroundTime(referenceTime); }
private void SetState(UserflowState newState, long nowTime) { // Establishes newState for userflow at nowTime . state = newState; isForegrounded = UserflowReporter.isForegrounded; switch (state) { case UserflowState.CANCELLED: SetEndTime(nowTime); RemoveTimer(); break; case UserflowState.BEGUN: SetBeginTime(nowTime); if (isForegrounded) { SetForegroundTime(nowTime); CreateTimer(); } break; default: // Final state SetEndTime(nowTime); RemoveTimer(); if (isForegrounded) { // Entering final state is effectively closing early ahead of // the time when app may be backgrounded later. The persisted // record gets the correct additional "eye time". eyeTime += nowTime - foregroundTime; isForegrounded = false; } if (newState == UserflowState.TIMEOUT) { Crittercism.OnUserflowTimeOut(new CRUserflowEventArgs(name)); } break; } UserflowReporter.Save(this); }
internal Userflow( string name, UserflowState state, int timeout, int value, Dictionary <string, string> metadata, long beginTime, long endTime, long eyeTime) { // This constructor only used by UserflowConvert ReadJson for finished // Userflow's appearing in either a UserflowReport or a Crash report. this.name = name; this.state = state; this.timeout = timeout; // milliseconds this.value = value; this.metadata = metadata; SetBeginTime(beginTime); // ticks SetEndTime(endTime); // ticks this.eyeTime = eyeTime; // ticks }