private void EndGenericAction(string actionId, string outcome, ErrorSource errorSource, string error, string errorDescription, string accountCid)
        {
            if (string.IsNullOrEmpty(actionId))
            {
                // This is an invalid action; we do not want to upload it.
                _errorStore.ReportError("Tried to end an action with an empty actionId", ErrorType.Action, ErrorSeverity.Warning);
                return;
            }

            ActionPropertyBag propertyBag = GetActionPropertyBagFromId(actionId);

            if (propertyBag == null)
            {
                _errorStore.ReportError("Trying to end an action that doesn't exist or was already uploaded", ErrorType.Action, ErrorSeverity.Warning);
                return;
            }

            if (propertyBag.ReadyForUpload)
            {
                return;
            }

            int startingCount = 1;
            var endTime       = DateTime.UtcNow;

            propertyBag.Add(ActionPropertyNames.OutcomeConstStrKey, outcome);
            propertyBag.Add(ActionPropertyNames.FailureSourceConstStrKey, MatsConverter.AsString(errorSource));
            propertyBag.Add(ActionPropertyNames.FailureConstStrKey, error);
            propertyBag.Add(ActionPropertyNames.FailureDescriptionConstStrKey, errorDescription);
            propertyBag.Add(ActionPropertyNames.EndTimeConstStrKey, DateTimeUtils.GetMillisecondsSinceEpoch(endTime));
            // propertyBag->Add(ActionPropertyNames::getAccountIdConstStrKey(), accountCid);  Commenting this out for GDPR reasons; once pipeline supports this we can upload again.
            propertyBag.Add(ActionPropertyNames.CountConstStrKey, startingCount);
            PopulateDuration(propertyBag);

            //Check if should aggregate here
            var contents = propertyBag.GetContents();

            if (_eventFilter.ShouldAggregateAction(contents))
            {
                EndAggregatedAction(actionId, propertyBag);
            }
            else
            {
                propertyBag.ReadyForUpload = true;
            }
        }
        /// <summary>
        /// Returns true if the actions are the same, either are ready for upload, and either is not aggregable.
        /// This is used to determine whether or not we should aggregate this particular property bag of event data.
        /// </summary>
        /// <param name="action1"></param>
        /// <param name="action2"></param>
        /// <returns></returns>
        public static bool IsEquivalentClass(ActionPropertyBag action1, ActionPropertyBag action2)
        {
            if (action1 == action2 || action1.ReadyForUpload || action2.ReadyForUpload || !action1.IsAggregable || !action2.IsAggregable)
            {
                return(false);
            }

            var contents1 = action1.GetContents();
            var contents2 = action2.GetContents();
            var v         = GetComparisonStringProperties();

            foreach (string s in v)
            {
                if (!IsPropertyEquivalent(s, contents1.StringProperties, contents2.StringProperties))
                {
                    return(false);
                }
            }

            return(true);
        }
        public static void AggregateActions(ActionPropertyBag targetAction, ActionPropertyBag childAction)
        {
            targetAction.IncrementCount();
            targetAction.Sum(ActionPropertyNames.CountConstStrKey, 1);

            var childContents = childAction.GetContents();

            foreach (string s in GetIntAggregationProperties())
            {
                AggregateMax(s, targetAction, childContents.IntProperties);
                AggregateMin(s, targetAction, childContents.IntProperties);
                AggregateSum(s, targetAction, childContents.IntProperties);
            }

            foreach (string s in GetInt64AggregationProperties())
            {
                AggregateMax(s, targetAction, childContents.Int64Properties);
                AggregateMin(s, targetAction, childContents.Int64Properties);
                AggregateSum(s, targetAction, childContents.Int64Properties);
            }
        }
        private void PopulateDuration(ActionPropertyBag propertyBag)
        {
            var  contents = propertyBag.GetContents();
            long startTime;
            long endTime;

            if (!contents.Int64Properties.TryGetValue(ActionPropertyNames.StartTimeConstStrKey, out startTime))
            {
                _errorStore.ReportError("Could not retrieve start time for duration calculation.", ErrorType.Action, ErrorSeverity.LibraryError);
                return;
            }

            if (!contents.Int64Properties.TryGetValue(ActionPropertyNames.EndTimeConstStrKey, out endTime))
            {
                _errorStore.ReportError("Could not retrieve end time for duration calculation.", ErrorType.Action, ErrorSeverity.LibraryError);
                return;
            }

            long duration = endTime - startTime;

            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.SumConstStrSuffix, duration);
            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.MaxConstStrSuffix, duration);
            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.MinConstStrSuffix, duration);
        }