# # concurrent events snapon # # This snapon calculates the number of concurrent events, using start time and duration values # concurrent_events = { version = "1.2" # ????-??-?? - GMF - 1.0 - Initial creation # 2012-05-08 - GMF - 1.1 - Added track_per_resource option # 2012-05-14 - GMF - 1.2 - Fixed prompting of track-per-resource options in UI label = "$lang_admin.snapons.concurrent_events.label" comment = "$lang_admin.snapons.concurrent_events.comment" config_snapon_category = "" parameters = { date_time_field = { parameter_value = "date_time" validation_type = "string" form_element_label = "$lang_admin.snapons.concurrent_events.parameters.date_time_field.form_element_label" form_element_type = "select" select_options_source = "database_fields" description = "" } # date_time_field duration_field = { parameter_value = "x_duration" validation_type = "string" form_element_label = "$lang_admin.snapons.concurrent_events.parameters.duration_field.form_element_label" form_element_type = "select" select_options_source = "database_fields" description = "" } # duration_field track_per_resource = { parameter_value = "false" form_element_label = "$lang_admin.snapons.concurrent_events.parameters.track_per_resource.form_element_label" form_element_type = "checkbox" description = "" } # track_per_resource resource_field = { parameter_value = "page" validation_type = "string" form_element_label = "$lang_admin.snapons.concurrent_events.parameters.resource_field.form_element_label" form_element_type = "select" select_options_source = "database_fields" description = "" } # resource_field concurrent_events_name = { parameter_value = "Concurrent events" # LM final_node_name = "session_entrances" validation_type = "field_label" validate_field_label_for = { database_fields = true report_fields = true } form_element_label = "{=capitalize(expand(lang_stats.field_labels.concurrent_events))=}" form_element_type = "text" form_element_width = "380" description = "" } # concurrent_events_name } # parameters parameters_form = { group_1 = { description = "$lang_admin.snapons.concurrent_events.parameters_form.group_1.description" parameters = { date_time_field = true duration_field = true concurrent_events_name = true track_per_resource = true resource_field = true } # parameters } # group 1 } # parameters_form attach_operations = { # When attaching: Add the concurrent events database fields add_database_field = { type = "add_database_fields" fields = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { label = "{= @parameters{'concurrent_events_name'}{'parameter_value'} =}" type = "int" derivation_method = "database_filter" aggregation_method = "max" category = "" index = "true" suppress_top = "0" suppress_bottom = "2" integer_bits = "0" } # concurrent_events_name } # fields } # add_database_field # When attaching: add the database filter to calculate the field add_database_filter = { type = "add_database_filters" filters = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { conditions = { # Sort chronologically sort = { type = "sort" fields = { date_time = { column_name = "{= @parameters{'date_time_field'}{'parameter_value'} =}" } } # fields } # sort # The current_events variable keeps track of how many events there are right now, for the current resource current_events = { type = "variable" variable_name = "current_events" variable_type = "node" initial_value = "" } # current_events # The current_events_all_resources node keeps track of how many events there are right now, for each resource current_events_all_resources = { type = "variable" variable_name = "current_events_all_resources" variable_type = "node" initial_value = "" } # current_events_all_resources future_event_end_date_times = { type = "variable" variable_name = "future_event_end_date_times" variable_type = "node" initial_value = "" } # future_event_end_date_times # This is the top-level node for future event information. It has one subnode for each resource, or one called "GLOBAL" if only one resource is tracked. future_event_all_resources_end_date_times = { type = "variable" variable_name = "future_event_all_resources_end_date_times" variable_type = "node" initial_value = "" } # future_event_all_resources_end_date_times future_event = { type = "variable" variable_name = "future_event" variable_type = "node" initial_value = "" } # future_event end_date_time = { type = "variable" variable_name = "end_date_time" variable_type = "int" initial_value = "" } # end_date_time nodepos = { type = "variable" variable_name = "nodepos" variable_type = "int" initial_value = "" } # nodepos min = { type = "variable" variable_name = "min" variable_type = "int" initial_value = "" } # min max = { type = "variable" variable_name = "max" variable_type = "int" initial_value = "" } # max mid = { type = "variable" variable_name = "mid" variable_type = "int" initial_value = "" } # mid midVal = { type = "variable" variable_name = "midVal" variable_type = "int" initial_value = "" } # midVal keyfound = { type = "variable" variable_name = "keyfound" variable_type = "bool" initial_value = "" } # keyfound } # conditions # The follow expression is the code which implements the filter action expression = ` {= # Set future_event_date_times to future_event_all_resources_end_date_times for global analysis, or to a subnode of it for per-resource analysis. if (@parameters{'track_per_resource'}{'parameter_value'}) then ( 'future_event_end_date_times = future_event_all_resources_end_date_times{' . @parameters{'resource_field'}{'parameter_value'} . '};\n'; 'current_events = current_events_all_resources{' . @parameters{'resource_field'}{'parameter_value'} . '};\n' . ); else ( 'future_event_end_date_times = future_event_all_resources_end_date_times;\n' . 'current_events = current_events_all_resources;\n' ); =} #echo("[" . {= @parameters{'resource_field'}{'parameter_value'} =} . "]: {= @parameters{'date_time_field'}{'parameter_value'} =}=" . {= @parameters{'date_time_field'}{'parameter_value'} =} . "; {= @parameters{'duration_field'}{'parameter_value'} =}=" . {= @parameters{'duration_field'}{'parameter_value'} =}); #echo("BEFORE: {= @parameters{'resource_field'}{'parameter_value'} =}=" . {= @parameters{'resource_field'}{'parameter_value'} =} . "; future_event_end_date_times=" . node_as_string(future_event_end_date_times)); # Pull all events off the front of the queue which are over, as of this timestamp. For each, decrement current_events and remove it from the queue. while ((num_subnodes(future_event_end_date_times) > 0) and (@(future_event_end_date_times[0]) < {= @parameters{'date_time_field'}{'parameter_value'} =})) ( # echo("[" . {= @parameters{'resource_field'}{'parameter_value'} =} . "]: Closing: " . @future_event_end_date_times[0] . "; subtracting " . @(future_event_end_date_times[0]){"count"}); @current_events -= @(future_event_end_date_times[0]){"count"}; delete_node(future_event_end_date_times[0]); ); # If duration is 0, don't count it as an event for concurrency calculations if ({= @parameters{'duration_field'}{'parameter_value'} =} != 0) then ( # echo("[" . {= @parameters{'resource_field'}{'parameter_value'} =} . "]: {= @parameters{'date_time_field'}{'parameter_value'} =}=" . {= @parameters{'date_time_field'}{'parameter_value'} =} . "; {= @parameters{'duration_field'}{'parameter_value'} =}=" . {= @parameters{'duration_field'}{'parameter_value'} =}); # This is an event, so mark that we have one more event now @current_events = @current_events + 1; # echo("[" . {= @parameters{'resource_field'}{'parameter_value'} =} . "]: Started new event; now @current_events=" . @current_events); end_date_time = {= @parameters{'date_time_field'}{'parameter_value'} =} + {= @parameters{'duration_field'}{'parameter_value'} =}; # echo(' end_date_time: ' . end_date_time); # Use a binary search to find this node # echo("Looking for " . end_date_time . " in " . node_as_string(future_event_end_date_times)); # min = 0; # max = num_subnodes(future_event_end_date_times); # if (max != 0) then ( # echo("starting with min=" . min . " and max=" . max); # nodepos = (min + max) / 2; # while ((min < max) and (end_date_time != @(future_event_end_date_times[nodepos]))) ( # echo("searching at nodepos=" . nodepos . "; min=" . min . "; max=" . max . "; @(future_event_end_date_times[" . nodepos . "])=" . @(future_event_end_date_times[nodepos])); # if (end_date_time <= @(future_event_end_date_times[nodepos])) then ( # max = nodepos - 1; # echo("Adjusted max; min=" . min . " and max=" . max); # ); # else ( # min = nodepos + 1; # echo("Adjusted min; min=" . min . " and max=" . max); # ); # nodepos = (min + max) / 2; # ); # #nodepos = (min + max) / 2; # ); # if non-empty # echo("Looking for " . end_date_time . " in " . node_as_string(future_event_end_date_times)); min = 0; max = num_subnodes(future_event_end_date_times) - 1; # int min; nodepos = -1; keyfound = false; while (min < max) ( nodepos = (min + max) / 2; midVal = @(future_event_end_date_times[nodepos]); #echo("min=" . min . "; max=" . max . "; midVal=" . midVal); if (midVal < end_date_time) then min = nodepos + 1; else if (midVal > end_date_time) then max = nodepos - 1; else ( # nodepos = mid; # keyfound keyfound = true; max = min; # exit loop ); #echo("continuing with min=" . min . "; max=" . max); ); #echo("keyfound=" . keyfound); if (!keyfound) then ( nodepos = min; #echo("nodepos=" . nodepos); if (nodepos >= num_subnodes(future_event_end_date_times)) then ( ); else if (@(future_event_end_date_times[nodepos]) < end_date_time) then nodepos++; #echo("Node not found; using nodepos=" . nodepos); ); # if (midVal > end_date_time) then # nodepos # if (nodepos == -1) then # nodepos = min; #echo("FOUND at nodepos=" . nodepos . "; min=" . min . "; max=" . max); # If we found a node smaller than end_date_time, move up to the next one # At this point, nodepos is the index of the node (0-indexed) we want to insert *before*; or it is num_subnodes(future_event_end_date_times) if we're adding to the end. #echo("num_subnodes(future_event_end_date_times): " . num_subnodes(future_event_end_date_times)); #if (nodepos < num_subnodes(future_event_end_date_times)) then # echo("@(future_event_end_date_times[" . nodepos . "]): " . @(future_event_end_date_times[nodepos])); # If this is a new node (if we didn't find this timestamp), create a new subnode with count=1 # if (nodepos >= num_subnodes(future_event_end_date_times)) then ( #echo("Inserting new node at position " . nodepos); # future_event = new_node(); # rename_node(future_event, thisrownum); # set_node_type(future_event, 'int'); # @future_event = end_date_time; # @future_event{"count"} = 1; # insert_node(future_event_end_date_times, future_event, nodepos); # ); # If we found a node less than this one, but didn't find this node, insert a new node after it with count=1. #if (@(future_event_end_date_times[nodepos]) != end_date_time) then ( # If we found a node smaller than end_date_time, move up to the next one #if (@(future_event_end_date_times[nodepos]) < end_date_time) then # nodepos++; if (!keyfound) then ( #echo("NOT FOUND, but can insert it at nodepos=" . nodepos); #echo("Inserting new node at position " . nodepos); future_event = new_node(); rename_node(future_event, thisrownum); set_node_type(future_event, 'int'); @future_event = end_date_time; @future_event{"count"} = 1; insert_node(future_event_end_date_times, future_event, nodepos); ); # Otherwise, increment the count of the existing node else ( #echo("FOUND at nodepos=" . nodepos); #echo("Incrementing node at position " . nodepos); @(future_event_end_date_times[nodepos]){"count"}++; ); #nodepos = 0; #while ((nodepos < num_subnodes(future_event_end_date_times)) and (@(future_event_end_date_times[nodepos]) <= end_date_time)) # nodepos++; # Remember the end of this event #set_node_type(future_event_end_date_times{thisrownum}, "int"); #@future_event_end_date_times{thisrownum} = {= @parameters{'date_time_field'}{'parameter_value'} =} + {= @parameters{'duration_field'}{'parameter_value'} =}; ##echo("CHECKING SORT (" . num_subnodes(future_event_end_date_times) . ")"); #node testn; #int lasttime = 0; #foreach testn future_event_end_date_times ( ##echo("consistency check: @testn=" . @testn . "; lasttime=" . lasttime . "; {= @parameters{'date_time_field'}{'parameter_value'} =}=" . {= @parameters{'date_time_field'}{'parameter_value'} =}); # if (@testn < lasttime) then ( # error("Internal: sort order check failed on " . node_as_string(future_event_end_date_times)); # ); # if (@testn < {= @parameters{'date_time_field'}{'parameter_value'} =}) then # error("Internal: date/time consistency check failed; date_time=" . {= @parameters{'date_time_field'}{'parameter_value'} =} . " but @testn=" . @testn . "; node=" . node_as_string(future_event_end_date_times)); ## echo("Sort confirmed for " . @testn); # lasttime = @testn; #); #echo("SORT/FUTURE CONSISTENCY CONFIRMED (" . num_subnodes(future_event_end_date_times) . " events)"); #echo("date_time=" . date_time . "; future_event_end_date_times: " . node_as_string(future_event_end_date_times)); #echo("AFTER: " . node_as_string(future_event_end_date_times)); ); # if duration != 0 # Mark the number of events for this row {= @parameters{'concurrent_events_name'}{'final_node_name'} =} = @current_events; #echo("[" . {= @parameters{'resource_field'}{'parameter_value'} =} . "]: Now @current_events=" . @current_events); ` } # concurrent_events_name } # filters } # add_sessions_database_filter # When attaching: Add the report field add_report_field = { type = "add_report_fields" fields = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { label = "{= @parameters{'concurrent_events_name'}{'parameter_value'} =}" column_label = "" column_info = "" database_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" display_format_type = "integer" show_remainder_value = true show_average_value = true show_min_value = true show_max_value = true show_total_value = true percent_calculation = "sum" } # concurrent_events_name } # fields } # add_report_field # When attaching: Add the report field to all xref groups add_xref_field = { type = "add_xref_fields" xref_group = "*" fields = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = "" } # fields } # add_xref_field # When attaching: Add the column to overview add_report_column_overview = { type = "add_report_element_columns" report = "overview" report_element = "overview" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns # When attaching: Add the column to day add_report_column_day = { type = "add_report_element_columns" report = "day" report_element = "day" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns_day # When attaching: Add the column to month add_report_column_month = { type = "add_report_element_columns" report = "month" report_element = "month" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns_month # When attaching: Add the column to year add_report_column_year = { type = "add_report_element_columns" report = "year" report_element = "year" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns_year # When attaching: Add the column to hour_of_day add_report_column_hour_of_day = { type = "add_report_element_columns" report = "hour_of_day" report_element = "hour_of_day" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns_hour_of_day # When attaching: Add the column to day_of_week add_report_column_day_of_week = { type = "add_report_element_columns" report = "day_of_week" report_element = "day_of_week" columns = { "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" = { report_field = "{= @parameters{'concurrent_events_name'}{'final_node_name'} =}" show_column = true show_percent_column = false show_bar_column = false show_graph = false } # concurrent_events_name } # columns } # add_report_columns_day_of_week } # attach_operations } # concurrent_events