public List <TimelineChartData> Get([FromUri] TimelineCriteria criteria) { return(TimelineVisualization.Get ( criteria, true )); }
public ActionResult GetCSVData(int scaleSetting, DateTime?timeFrom, DateTime?timeTo, int?timeout, bool?grayscale, bool?realtime, int courseId, string[] userId) { bool gray = grayscale ?? false; TimelineCriteria var = new TimelineCriteria { timeScale = (TimeScale)scaleSetting, timeFrom = timeFrom, timeTo = timeTo, timeout = timeout, grayscale = gray, courseId = courseId, userIds = string.Join(",", userId) }; var chartCsvData = TimelineVisualization.GetCSV(var, realtime); return(File(new System.Text.UTF8Encoding().GetBytes(chartCsvData), "text/csv", "timeline.csv")); }
public static string GetCSV(TimelineCriteria criteria, bool?realtime) { var csvText = new StringBuilder(); var chartData = Get(criteria, realtime); chartData.ForEach(x => { csvText.Append(x.UserId); var i = 0; var j = 0; while (i < x.measures.Count || j < x.markers.Count) { if (i >= x.measures.Count) { csvText.AppendFormat(EVENT_FORMAT, x.markers[j].Name, x.markers[j].EventTime.ToString(TIME_FORMAT)); j++; } else if (j >= x.markers.Count) { csvText.AppendFormat(ACTIVITY_FORMAT, x.measures[i].Name, x.measures[i].StartTime.ToString(TIME_FORMAT), x.measures[i].EndTime.ToString(TIME_FORMAT)); i++; } else { if (x.markers[j].Position < x.measures[i].StartPoint) { csvText.AppendFormat(EVENT_FORMAT, x.markers[j].Name, x.markers[j].EventTime.ToString(TIME_FORMAT)); j++; } else { csvText.AppendFormat(ACTIVITY_FORMAT, x.measures[i].Name, x.measures[i].StartTime.ToString(TIME_FORMAT), x.measures[i].EndTime.ToString(TIME_FORMAT)); i++; } } } csvText.AppendFormat("{0}", Environment.NewLine); }); return(csvText.ToString()); }
public static List <TimelineChartData> Get(TimelineCriteria criteria, bool?realtime) { using (var sqlConnection = new SqlConnection(StringConstants.ConnectionString)) { sqlConnection.Open(); var defaultMin = new DateTime(2000, 1, 1); var defaultMax = DateTime.Today.AddDays(1); var timeFrom = criteria.timeFrom ?? defaultMin;; var timeTo = criteria.timeTo ?? defaultMax; var timeoutVal = criteria.timeout ?? VisualizationParams.DEFAULT_TIMEOUT; var rawR = sqlConnection.Query <StateMachineEvent>(@"GetStateMachineEvents", new { dateFrom = timeFrom, dateTo = timeTo, courseId = criteria.courseId, userIds = criteria.userIds }, commandType: CommandType.StoredProcedure) .ToList(); sqlConnection.Close(); if (rawR.Count == 0) { return(null); } if (timeFrom == defaultMin) { timeFrom = rawR.Min(x => x.EventDate); } if (timeTo == defaultMax) { timeTo = rawR.Max(x => x.EventDate); } var totalMinutes = (int)(timeTo - timeFrom).TotalMinutes; // chart data timeline var chartData = new List <TimelineChartData>(); var currentSolution = string.Empty; // previous build error is used for figuring out the next state of start without debugging event var lastBuildErrorLogId = rawR.First().BuildErrorLogId; foreach (var r in rawR) { // processing the flat events and compose a list of programming states for each user // the composed list of programming states are ordered by the event time // so the output is a collection of state timelines for the list of users var currEventTime = r.EventDate; var currEventPoint = (currEventTime - timeFrom).TotalMinutes; // init local variables ProgrammingState prevStateName; if (string.Compare(r.SolutionName, currentSolution, StringComparison.OrdinalIgnoreCase) != 0) { currentSolution = r.SolutionName; prevStateName = ProgrammingState.edit_syn_u_sem_u; } // process one user at a time var userData = chartData.SingleOrDefault(x => x.UserId == r.SenderId); // a new user's timeline? if (userData == null) { // yes, create a new timeline entry #region a new user timeline // since each bullet svg is drawn independently // the high level information needs to be passed into each svg drawing routine userData = new TimelineChartData { UserId = r.SenderId, title = string.Format("{0} {1}", r.FirstName, r.LastName), markers = new List <Point>(), measures = new List <State>(), }; // is the first event in idle timeout range? if ((currEventTime - timeFrom).TotalMinutes > timeoutVal) { // no, insert an idle state from the beginning to now prevStateName = ProgrammingState.idle; var uiproperties = TimelineStateDictionaries.UIProperties[prevStateName]; var idleState = new State { ProgrammingState = prevStateName, Name = uiproperties.Label, CssClass = criteria.grayscale ? uiproperties.CssGray : uiproperties.Css, StartTime = timeFrom, ShiftedStartTime = timeFrom, StartPoint = 0, EndTime = currEventTime, EndPoint = currEventPoint, }; userData.measures.Add(idleState); } else { // yes, insert a default programming state prevStateName = ProgrammingState.edit_syn_u_sem_u; var uiproperties = TimelineStateDictionaries.UIProperties[prevStateName]; var defaultState = new State { ProgrammingState = prevStateName, Name = uiproperties.Label, CssClass = criteria.grayscale ? uiproperties.CssGray : uiproperties.Css, StartTime = timeFrom, ShiftedStartTime = timeFrom, StartPoint = 0, EndTime = timeTo, EndPoint = totalMinutes, }; userData.measures.Add(defaultState); } chartData.Add(userData); #endregion // reset for a new user lastBuildErrorLogId = null; } // is this a social event? if (!string.IsNullOrWhiteSpace(r.MarkerType)) { // yes, add social media event marker userData.markers.Add(new Point { Name = r.MarkerType, Position = currEventPoint, EventTime = r.EventDate }); } else { // no, transition to next state based on the previous state // non-social event could be a state transition trigger // first to check the timespan between current and previous non-idle state events #region insert an idle state if the timespan between this event and previous event is too long var prevState = userData.measures.Last(); var prevEventTime = prevState.ShiftedStartTime; prevStateName = prevState.ProgrammingState; if ((currEventTime - prevEventTime).TotalMinutes > timeoutVal) { if (prevStateName == ProgrammingState.idle) { // extend the initial idle state to avoid the extra rectangle prevState.EndTime = currEventTime; prevState.EndPoint = currEventPoint; } else { #region add a new idle state prevStateName = ProgrammingState.idle; var uiproperties = TimelineStateDictionaries.UIProperties[prevStateName]; var idleState = new State { ProgrammingState = prevStateName, Name = uiproperties.Label, CssClass = criteria.grayscale ? uiproperties.CssGray : uiproperties.Css, StartTime = prevEventTime.AddMinutes(timeoutVal), StartPoint = (prevEventTime - timeFrom).TotalMinutes + timeoutVal, EndTime = currEventTime, EndPoint = currEventPoint, }; userData.measures.Add(idleState); // terminate previous non-idle state prevState.EndTime = idleState.StartTime; prevState.EndPoint = idleState.StartPoint; prevState = idleState; #endregion } } #endregion // resolve next state name from current state and current event #region get next programming state from state dictionaries var nextStateName = prevStateName; if (prevStateName == ProgrammingState.idle && prevState.StartTime > timeFrom) { // if currently in idle but not the first idle state, use the last non-idle state to calculate the next state prevStateName = userData.measures.Last(x => x.ProgrammingState != ProgrammingState.idle).ProgrammingState; } // look up next programming state from the transition tables if (String.Compare(r.LogType, "BuildEvent", StringComparison.OrdinalIgnoreCase) == 0) { nextStateName = r.BuildErrorLogId.HasValue ? TimelineStateDictionaries.NextStateForBuildWithError[prevStateName] : TimelineStateDictionaries.NextStateForBuildWithoutError[prevStateName]; lastBuildErrorLogId = r.BuildErrorLogId.HasValue ? (int?)r.BuildErrorLogId.Value : null; } else if (String.Compare(r.LogType, "DebugEvent", StringComparison.OrdinalIgnoreCase) == 0 && r.ExecutionActionId.HasValue) { nextStateName = GetNextStateForDebugEvent(userData, prevStateName, r.ExecutionActionId.Value, lastBuildErrorLogId); // the next event's previous event (this one) is not a build event lastBuildErrorLogId = null; } else if (String.Compare(r.LogType, "ExceptionEvent", StringComparison.OrdinalIgnoreCase) == 0 && (prevStateName == ProgrammingState.debug_sem_u || prevStateName == ProgrammingState.idle)) { nextStateName = TimelineStateDictionaries.NextStateForExceptionEvent[prevStateName]; // the next event's previous event (this one) is not a build event lastBuildErrorLogId = null; } else if (TimelineStateDictionaries.EditorEvents.Any(x => String.Compare(x, r.LogType, StringComparison.OrdinalIgnoreCase) == 0)) { nextStateName = TimelineStateDictionaries.NextStateForEditorEvent[prevStateName]; } #endregion // terminate previous state and append the next state if it's changed if (nextStateName != prevState.ProgrammingState) { #region append new state // idle state is instantiated with a valid termination date // when idle state is created, it also terminates the previous state (timeout) if (prevState.EndTime == timeTo) { // terminate previous non-idle state prevState.EndTime = currEventTime; prevState.EndPoint = currEventPoint; } var uiproperties = TimelineStateDictionaries.UIProperties[nextStateName]; userData.measures.Add(new State { // new state ProgrammingState = nextStateName, Name = uiproperties.Label, CssClass = criteria.grayscale ? uiproperties.CssGray : uiproperties.Css, StartTime = currEventTime, ShiftedStartTime = currEventTime, StartPoint = currEventPoint, EndTime = timeTo, EndPoint = totalMinutes, }); #endregion } else { // update continous state start time so it won't timeout to idle state prevState.ShiftedStartTime = currEventTime; } } } #region append idle state to the end if necessary chartData.ForEach(x => { var lastMeasure = x.measures.Last(); if (lastMeasure.EndTime == timeTo && (timeTo - lastMeasure.ShiftedStartTime).TotalMinutes > timeoutVal) { // prepare a new idle state for the end var uiproperties = TimelineStateDictionaries.UIProperties[ProgrammingState.idle]; var idleState = new State { ProgrammingState = ProgrammingState.idle, Name = uiproperties.Label, CssClass = criteria.grayscale ? uiproperties.CssGray : uiproperties.Css, StartTime = lastMeasure.StartTime.AddMinutes(timeoutVal), StartPoint = lastMeasure.StartPoint + timeoutVal, EndTime = lastMeasure.EndTime, EndPoint = lastMeasure.EndPoint, }; x.measures.Add(idleState); // terminate the lastMeasure state lastMeasure.EndTime = idleState.StartTime; lastMeasure.EndPoint = idleState.StartPoint; } else if (lastMeasure.ProgrammingState == ProgrammingState.idle) { lastMeasure.EndTime = timeTo; lastMeasure.EndPoint = totalMinutes; } }); #endregion // only show horizontal x-axis for the last series if (chartData.Count > 0) { chartData.Last().showTicks = true; } return(chartData); } }