예제 #1
0
        public void IsBitSetIsCorrectForDailyMap()
        {
            // Day Offset             3210 9876     5432 1098     7654 3210
            var map          = new byte[] { 0b_1000_0000, 0b_0010_0000, 0b_0000_0100 };
            var mapStartDate = new DateTime(2019, 1, 1);

            for (var dayOffset = -5; dayOffset < 100; dayOffset++)
            {
                var date  = mapStartDate.AddDays(dayOffset);
                var isSet = StreakTypeService.IsBitSet(map, mapStartDate, date, StreakOccurrenceFrequency.Daily, out var errorMessage);

                if (dayOffset < 0)
                {
                    // Should get error about checking a bit that is pre-start-date
                    Assert.NotEmpty(errorMessage);
                }
                else
                {
                    Assert.Empty(errorMessage);

                    // The day of the month is the offset + 1 since offset 0 is Jan 1, 2019
                    if (date.Year == 2019 && date.Month == 1 && (date.Day == 3 || date.Day == 14 || date.Day == 24))
                    {
                        Assert.True(isSet);
                    }
                    else
                    {
                        Assert.False(isSet);
                    }
                }
            }
        }
예제 #2
0
        public void GetFrequencyUnitDifferenceExclusiveDaily()
        {
            var frequency   = StreakOccurrenceFrequency.Daily;
            var isInclusive = false;

            // Month of January is 31 days long
            var startDate = new DateTime(2019, 1, 1);
            var endDate   = new DateTime(2019, 1, 31);
            var result    = StreakTypeService.GetFrequencyUnitDifference(startDate, endDate, frequency, isInclusive);

            Assert.Equal(30, result);

            // Year of 2019 is 365 days long
            startDate = new DateTime(2019, 1, 1);
            endDate   = new DateTime(2019, 12, 31);
            result    = StreakTypeService.GetFrequencyUnitDifference(startDate, endDate, frequency, isInclusive);
            Assert.Equal(364, result);

            // Negative calculation is okay
            startDate = new DateTime(2019, 1, 1);
            endDate   = new DateTime(2018, 12, 31);
            result    = StreakTypeService.GetFrequencyUnitDifference(startDate, endDate, frequency, isInclusive);
            Assert.Equal(-1, result);

            // Same day calculation is 0
            result = StreakTypeService.GetFrequencyUnitDifference(startDate, startDate, frequency, isInclusive);
            Assert.Equal(0, result);
        }
예제 #3
0
        /// <summary>
        /// Get the map being edited
        /// </summary>
        /// <returns></returns>
        private byte[] GetTargetMap()
        {
            var savedMapState = ViewState[ViewStateKey.Map].ToStringSafe();

            if (!savedMapState.IsNullOrWhiteSpace())
            {
                return(StreakTypeService.GetMapFromHexDigitString(savedMapState));
            }

            if (IsTargetingEngagementMap())
            {
                var isEngagementExclusionMap = GetAttributeValue(AttributeKey.IsEngagementExclusion).AsBoolean();
                return(isEngagementExclusionMap ? GetStreak().ExclusionMap : GetStreak().EngagementMap);
            }

            if (IsTargetingExclusionMap())
            {
                return(GetStreakTypeExclusion().ExclusionMap);
            }

            if (IsTargetingOccurrenceMap())
            {
                return(GetStreakType().OccurrenceMap);
            }

            return(null);
        }
예제 #4
0
        public void GetHexDigitStringFromMapWorksCorrectly()
        {
            var map            = new byte[] { 0x_ab, 0x_cd, 0x_ef, 0x_01, 0x_23, 0x_45, 0x_67, 0x_89 };
            var expectedString = "ABCDEF0123456789";

            var result = StreakTypeService.GetHexDigitStringFromMap(map);

            Assert.That.AreEqual(expectedString, result);
        }
        /// <summary>
        /// Executes this instance.
        /// </summary>
        /// <exception cref="System.NotImplementedException"></exception>
        public void Execute()
        {
            if (StreakTypeId == default)
            {
                return;
            }

            StreakTypeService.HandlePostSaveChanges(StreakTypeId);
        }
예제 #6
0
        /// <summary>
        /// Executes this instance.
        /// </summary>
        /// <param name="message"></param>
        public override void Execute(Message message)
        {
            StreakTypeService.RebuildStreakType(null, message.StreakTypeId, out var errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ExceptionLogService.LogException(errorMessage);
            }
        }
예제 #7
0
        public void ResetBitWorksForDailyMap()
        {
            // Offset             7654 3210
            const byte byte2 = 0b_0000_0100;

            // Offset             5432 1098
            const byte byte1 = 0b_0010_0000;

            // Offset             3210 9876
            const byte byte0 = 0b_1000_0000;

            var map       = new byte[] { byte0, byte1, byte2 };
            var startDate = new DateTime(2019, 1, 1);
            var frequency = StreakOccurrenceFrequency.Daily;

            // Reset a bit before the start date and get an error
            var result = StreakTypeService.SetBit(map, startDate, startDate.AddDays(-1), frequency, false, out var errorMessage);

            Assert.NotEmpty(errorMessage);  // Verify error occurred
            Assert.Same(result, map);       // Verify in-place operation
            Assert.Equal(byte0, result[0]); // Verify no changes
            Assert.Equal(byte1, result[1]); // Verify no changes
            Assert.Equal(byte2, result[2]); // Verify no changes

            // Reset a bit that is already 0
            result = StreakTypeService.SetBit(map, startDate, startDate.AddDays(0), frequency, false, out errorMessage);
            Assert.Empty(errorMessage);     // Verify no error
            Assert.Same(result, map);       // Verify in-place operation
            Assert.Equal(byte0, result[0]); // Verify no changes
            Assert.Equal(byte1, result[1]); // Verify no changes
            Assert.Equal(byte2, result[2]); // Verify no changes

            // Reset the first set bit
            result = StreakTypeService.SetBit(map, startDate, startDate.AddDays(2), frequency, false, out errorMessage);
            Assert.Empty(errorMessage);     // Verify no errors
            Assert.Same(result, map);       // Verify in-place operation
            Assert.Equal(0, result[2]);     // Verify change
            Assert.Equal(byte1, result[1]); // Verify no changes
            Assert.Equal(byte0, result[0]); // Verify no changes

            // Reset a bit beyond the array and force it to grow
            result = StreakTypeService.SetBit(map, startDate, startDate.AddDays(24), frequency, false, out errorMessage);
            Assert.Empty(errorMessage);   // Verify no errors
            Assert.NotSame(result, map);  // Verify memory allocation occurred for new array
            var newLength = 128;

            Assert.True(result.Length == newLength);    // Verify the array grew to the next multiple of 128
            Assert.Equal(0, result[newLength - 1]);     // Verify no additional changes
            Assert.Equal(byte1, result[newLength - 2]); // Verify no changes
            Assert.Equal(byte0, result[newLength - 3]); // Verify no changes

            // Verify all other bytes are unset
            for (var i = 0; i < (newLength - 3); i++)
            {
                Assert.Equal(0, result[i]);
            }
        }
        /// <summary>
        /// Executes this instance.
        /// </summary>
        /// <param name="message"></param>
        public override void Execute(Message message)
        {
            if (message.StreakTypeId == default)
            {
                return;
            }

            StreakTypeService.HandlePostSaveChanges(message.StreakTypeId);
        }
예제 #9
0
        /// <summary>
        /// Get the streak type service
        /// </summary>
        /// <returns></returns>
        private StreakTypeService GetStreakTypeService()
        {
            if (_streakTypeService == null)
            {
                var rockContext = GetRockContext();
                _streakTypeService = new StreakTypeService(rockContext);
            }

            return(_streakTypeService);
        }
예제 #10
0
        /// <summary>
        /// Shows the controls needed
        /// </summary>
        private void RenderCheckboxes()
        {
            nbMessage.Text = string.Empty;
            var streakType = GetStreakType();

            if (streakType == null)
            {
                ShowBlockError(nbMessage, "A streak type is required.");
                return;
            }

            if (!CanEdit())
            {
                SetVisible(false);
                return;
            }

            lTitle.Text = GetTargetMapTitle();
            var map          = GetTargetMap();
            var errorMessage = string.Empty;

            var isDaily   = streakType.OccurrenceFrequency == StreakOccurrenceFrequency.Daily;
            var dateRange = GetDateRange();
            var startDate = dateRange.Start.Value;
            var endDate   = dateRange.End.Value;

            if (!isDaily)
            {
                startDate = startDate.SundayDate();
                endDate   = endDate.SundayDate();
            }

            cblCheckboxes.Label = isDaily ? "Days" : "Weeks";
            cblCheckboxes.Items.Clear();

            var minDate       = GetMinDate();
            var maxDate       = isDaily ? RockDateTime.Today : RockDateTime.Today.SundayDate();
            var checkboxCount = StreakTypeService.GetFrequencyUnitDifference(startDate, endDate, streakType.OccurrenceFrequency, true);

            for (var i = 0; i < checkboxCount; i++)
            {
                var representedDate = startDate.AddDays(isDaily ? i : (i * DaysPerWeek));

                cblCheckboxes.Items.Add(new ListItem
                {
                    Enabled  = representedDate >= minDate && representedDate <= maxDate,
                    Selected = StreakTypeService.IsBitSet(map, streakType.StartDate, representedDate, streakType.OccurrenceFrequency, out errorMessage),
                    Text     = GetLabel(isDaily, representedDate),
                    Value    = representedDate.ToISO8601DateString()
                });
            }

            cblCheckboxes.DataBind();
        }
예제 #11
0
        /// <summary>
        /// Executes this instance.
        /// </summary>
        public void Execute()
        {
            ReportProgress(0);
            StreakTypeService.RebuildStreakType(Progress, StreakTypeId, out var errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ExceptionLogService.LogException(errorMessage);
            }

            ReportProgress(100);
        }
예제 #12
0
        public void GetMapFromHexDigitStringWorksCorrectly()
        {
            var expectedBytes = new byte[] { 0x_ab, 0x_cd, 0x_ef, 0x_01, 0x_23, 0x_45, 0x_67, 0x_89 };
            var hexString     = "ABCDEF0123456789";

            var result = StreakTypeService.GetMapFromHexDigitString(hexString);

            Assert.That.AreEqual(expectedBytes.Length, result.Length);

            for (var i = 0; i < expectedBytes.Length; i++)
            {
                Assert.That.AreEqual(expectedBytes[i], result[i]);
            }
        }
예제 #13
0
        /// <summary>
        /// Logs the interactions.
        /// </summary>
        /// <param name="interactionTransactionInfos">The interaction transaction infos to process.</param>
        /// <param name="rockContext">The rock context.</param>
        private void LogInteractions(List <InteractionTransactionInfo> interactionTransactionInfos, RockContext rockContext)
        {
            List <Interaction> interactionsToInsert = new List <Interaction>();

            var interactionService = new InteractionService(rockContext);

            foreach (var info in interactionTransactionInfos.Where(a => a.InteractionComponentId.HasValue))
            {
                /*
                 * 2020-06-29 - JH
                 *
                 * The 'CreateInteraction(...)' method called below sets the following properties on the Interaction object:
                 *
                 * - InteractionComponentId
                 * - InteractionDateTime (but with the wrong value)
                 * - InteractionSessionId
                 * - Source
                 * - Medium
                 * - Campaign
                 * - Content
                 * - Term
                 */
                var interaction = interactionService.CreateInteraction(info.InteractionComponentId.Value, info.UserAgent, info.InteractionData, info.IPAddress, info.BrowserSessionId);

                // The rest of the properties need to be manually set.
                interaction.EntityId               = info.InteractionEntityId;
                interaction.Operation              = info.InteractionOperation.IsNotNullOrWhiteSpace() ? info.InteractionOperation.Trim() : "View";
                interaction.InteractionSummary     = info.InteractionSummary?.Trim();
                interaction.PersonAliasId          = info.PersonAliasId;
                interaction.InteractionDateTime    = info.InteractionDateTime;
                interaction.InteractionTimeToServe = info.InteractionTimeToServe;
                interaction.RelatedEntityTypeId    = info.InteractionRelatedEntityTypeId;
                interaction.RelatedEntityId        = info.InteractionRelatedEntityId;
                interaction.ChannelCustom1         = info.InteractionChannelCustom1?.Trim();
                interaction.ChannelCustom2         = info.InteractionChannelCustom2?.Trim();
                interaction.ChannelCustomIndexed1  = info.InteractionChannelCustomIndexed1?.Trim();

                interaction.SetInteractionData(info.InteractionData?.Trim());
                interactionsToInsert.Add(interaction);
            }

            rockContext.BulkInsert(interactionsToInsert);

            // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those
            // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this
            // transaction can continue processing and exit, so update the streak using a task.
            interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i)));
        }
예제 #14
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(IJobExecutionContext context)
        {
            var streakTypeId = context.JobDetail.JobDataMap.GetString(DataMapKey.StreakTypeId).AsInteger();

            StreakTypeService.RebuildStreakTypeFromAttendance(streakTypeId, out var errorMessage);

            if (errorMessage.IsNullOrWhiteSpace())
            {
                context.Result = string.Format("Streak maps have been rebuilt for streak type id {0}", streakTypeId);
                return;
            }

            var exception = new Exception(errorMessage);
            var context2  = HttpContext.Current;

            ExceptionLogService.LogException(exception, context2);

            throw exception;
        }
예제 #15
0
        /// <summary>
        /// Write the map to the view state but not to the database
        /// </summary>
        private void SaveMapState()
        {
            var streakType   = GetStreakType();
            var map          = GetTargetMap();
            var errorMessage = string.Empty;

            foreach (ListItem checkbox in cblCheckboxes.Items)
            {
                var representedDate = checkbox.Value.AsDateTime();

                if (representedDate.HasValue)
                {
                    map = StreakTypeService.SetBit(map, streakType.StartDate, representedDate.Value,
                                                   streakType.OccurrenceFrequency, checkbox.Selected, out errorMessage);
                }
            }

            ViewState[ViewStateKey.Map] = StreakTypeService.GetHexDigitStringFromMap(map);
        }
예제 #16
0
        /// <summary>
        /// Get the map being edited
        /// </summary>
        /// <returns></returns>
        private DateTime GetMinDate()
        {
            var streakType      = GetStreakType();
            var streakTypeCache = GetStreakTypeCache();
            var minDate         = StreakTypeService.AlignDate(streakType.StartDate, streakTypeCache);

            if (IsTargetingEngagementMap())
            {
                var enrollment     = GetStreak();
                var enrollmentDate = StreakTypeService.AlignDate(enrollment.EnrollmentDate, streakTypeCache);

                if (enrollmentDate > minDate)
                {
                    minDate = enrollmentDate;
                }
            }

            return(minDate);
        }
예제 #17
0
        /// <summary>
        /// Rebuild the enrollment data
        /// </summary>
        private void RebuildData()
        {
            var rockContext = GetRockContext();
            var streak      = GetStreak();

            if (!streak.IsAuthorized(Authorization.ADMINISTRATE, CurrentPerson))
            {
                mdDeleteWarning.Show("You are not authorized to rebuild this item.", ModalAlertType.Information);
                return;
            }

            var errorMessage = string.Empty;

            StreakTypeService.RebuildStreakFromAttendance(streak.StreakTypeId, streak.PersonAlias.PersonId, out errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ShowBlockError(nbEditModeMessage, errorMessage);
                return;
            }

            ShowBlockSuccess(nbEditModeMessage, "The streak rebuild was successful!");
        }
예제 #18
0
        public void GetAggregateMap()
        {
            var map1 = new byte[] { 0b_1000_0000, 0b_0010_0000, 0b_1000_0100 };
            var map2 = new byte[] { 0b_1001_0000, 0b_0010_0100, 0b_0000_0100, 0b_1111_0101 };
            var map3 = new byte[] { };

            var result = StreakTypeService.GetAggregateMap(new byte[][] { map1, map2, map3 });

            // Verify map 1 didn't change
            Assert.That.AreEqual(3, map1.Length);
            Assert.That.AreEqual(0b_1000_0000, map1[0]);
            Assert.That.AreEqual(0b_0010_0000, map1[1]);
            Assert.That.AreEqual(0b_1000_0100, map1[2]);

            // Verify map 2 didn't change
            Assert.That.AreEqual(4, map2.Length);
            Assert.That.AreEqual(0b_1001_0000, map2[0]);
            Assert.That.AreEqual(0b_0010_0100, map2[1]);
            Assert.That.AreEqual(0b_0000_0100, map2[2]);
            Assert.That.AreEqual(0b_1111_0101, map2[3]);

            // Verify map3 didn't change
            Assert.That.AreEqual(0, map3.Length);

            // Verify that the result is a new array, consisting of bytes OR'ed together
            Assert.That.AreEqual(128, result.Length);
            Assert.That.AreEqual(map1[2] | map2[3], result[128 - 1]);
            Assert.That.AreEqual(map1[1] | map2[2], result[128 - 2]);
            Assert.That.AreEqual(map1[0] | map2[1], result[128 - 3]);
            Assert.That.AreEqual(map2[0], result[128 - 4]);

            // Check all the other bytes are 0
            for (var i = 0; i < (128 - 4); i++)
            {
                Assert.That.AreEqual(0, result[i]);
            }
        }
예제 #19
0
        /// <summary>
        /// Get the default end date that should be used for the date picker
        /// </summary>
        /// <returns></returns>
        private DateTime GetDefaultEndDate()
        {
            var streakTypeCache = GetStreakTypeCache();

            return(StreakTypeService.AlignDate(RockDateTime.Now, streakTypeCache));
        }
예제 #20
0
        /// <summary>
        /// Create new attempt records and return them in a list. All new attempts should be after the most recent successful attempt.
        /// </summary>
        /// <param name="achievementTypeCache">The achievement type cache.</param>
        /// <param name="streak">The streak.</param>
        /// <param name="mostRecentClosedAttempt">The most recent closed attempt.</param>
        /// <returns></returns>
        protected override List <AchievementAttempt> CreateNewAttempts(AchievementTypeCache achievementTypeCache, Streak streak, AchievementAttempt mostRecentClosedAttempt)
        {
            var rockContext       = new RockContext();
            var streakTypeService = new StreakTypeService(rockContext);
            var streakTypeCache   = StreakTypeCache.Get(streak.StreakTypeId);

            // Validate the attribute values
            var numberToAchieve = GetAttributeValue(achievementTypeCache, AttributeKey.NumberToAchieve).AsInteger();

            if (numberToAchieve <= 0)
            {
                ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts cannot process because the NumberToAchieve attribute is less than 1");
                return(null);
            }

            var attributeTimespanDays = GetAttributeValue(achievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull();

            if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0)
            {
                ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts cannot process because the TimespanInDays attribute is less than 1");
                return(null);
            }

            // Calculate the date range where new achievements can be validly found
            var attributeMinDate = GetAttributeValue(achievementTypeCache, AttributeKey.StartDateTime).AsDateTime();
            var attributeMaxDate = GetAttributeValue(achievementTypeCache, AttributeKey.EndDateTime).AsDateTime();
            var minDate          = CalculateMinDateForAchievementAttempt(streak.EnrollmentDate, mostRecentClosedAttempt, attributeMinDate, numberToAchieve);
            var maxDate          = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate);

            // Get the max date that streaks can be broken. This is to avoid breaking streaks while people still have time to
            // engage in that day or week (because it is the current day or week)
            var maxDateForStreakBreaking = StreakTypeService.GetMaxDateForStreakBreaking(streakTypeCache);

            // Track the attempts in a list that will be returned. The int is the streak count for that attempt
            var attempts = new List <AchievementAttempt>();
            var streaks  = new List <ComputedStreak>();

            // Define what happens for each bit in the date range
            bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion)
            {
                // If there is an engagement and a timespan, then this is a possible attempt. If there is no timespan then only one
                // attempt needs to be tracked at a time
                if (hasOccurrence && hasEngagement && (attributeTimespanDays.HasValue || !streaks.Any()))
                {
                    streaks.Add(new ComputedStreak(currentDate));
                }
                else if (hasOccurrence && !hasEngagement && !hasExclusion && streaks.Any())
                {
                    // Break the streaks and close an attempt if there is an unexcused absence
                    var longestStreak = streaks.First();
                    attempts.Add(GetAttemptFromStreak(longestStreak, numberToAchieve, currentDate <= maxDateForStreakBreaking));
                    streaks.Clear();
                    return(false);
                }

                for (var i = streaks.Count - 1; i >= 0; i--)
                {
                    var computedStreak = streaks[i];

                    if (hasOccurrence && hasEngagement)
                    {
                        // Increment the streak
                        computedStreak.Count++;
                        computedStreak.EndDate = currentDate;

                        // Check for a fulfilled attempt
                        if (computedStreak.Count >= numberToAchieve)
                        {
                            streaks.Clear();

                            if (achievementTypeCache.AllowOverAchievement)
                            {
                                streaks.Add(computedStreak);
                                i = 0;
                            }
                            else
                            {
                                attempts.Add(GetAttemptFromStreak(computedStreak, numberToAchieve, !achievementTypeCache.AllowOverAchievement));
                                break;
                            }
                        }
                    }

                    // If there is a timespan and this streak is too old, then the attempt is closed
                    if (attributeTimespanDays.HasValue)
                    {
                        var inclusiveAge = (currentDate - computedStreak.StartDate).Days + 1;

                        if (inclusiveAge >= attributeTimespanDays.Value)
                        {
                            var timedOutAttempt = GetAttemptFromStreak(computedStreak, numberToAchieve, true);
                            attempts.Add(timedOutAttempt);
                            streaks.RemoveAt(i);

                            // Remove more recently started streaks that started before the next valid start date (based
                            // on the deficiency of this timed out attempt)
                            var nextValidStartDate = CalculateMinDateForAchievementAttempt(streak.EnrollmentDate, timedOutAttempt, attributeMinDate, numberToAchieve);

                            for (var j = streaks.Count - 1; j >= i; j--)
                            {
                                var moreRecentStreak = streaks[j];

                                if (moreRecentStreak.StartDate < nextValidStartDate)
                                {
                                    streaks.RemoveAt(j);
                                }
                            }
                        }
                    }
                }

                return(false);
            }

            // Iterate through the streak date for the date range specified
            streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts got an error calling StreakTypeService.IterateStreakMap: {errorMessage}");
                return(null);
            }

            // The longest leftover streak is an open attempt
            if (streaks.Any())
            {
                var longestStreak = streaks.First();
                attempts.Add(GetAttemptFromStreak(longestStreak, numberToAchieve, false));
            }

            return(attempts);
        }
예제 #21
0
        /// <summary>
        /// Update the open attempt record if there are changes.
        /// </summary>
        /// <param name="openAttempt"></param>
        /// <param name="achievementTypeCache">The achievement type cache.</param>
        /// <param name="streak">The streak.</param>
        protected override void UpdateOpenAttempt(AchievementAttempt openAttempt, AchievementTypeCache achievementTypeCache, Streak streak)
        {
            var rockContext       = new RockContext();
            var streakTypeService = new StreakTypeService(rockContext);
            var streakTypeCache   = GetStreakTypeCache(achievementTypeCache);

            // Validate the attribute values
            var numberToAchieve = GetAttributeValue(achievementTypeCache, AttributeKey.NumberToAchieve).AsInteger();

            if (numberToAchieve <= 0)
            {
                ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt cannot process because the numberToAchieve attribute is less than 1");
                return;
            }

            var attributeTimespanDays = GetAttributeValue(achievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull();

            if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0)
            {
                ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt cannot process because the TimespanInDays attribute is less than 1");
                return;
            }

            // Calculate the date range where the open attempt can be validly fulfilled
            var attributeMaxDate = GetAttributeValue(achievementTypeCache, AttributeKey.EndDateTime).AsDateTime();
            var minDate          = openAttempt.AchievementAttemptStartDateTime;
            var maxDate          = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate);

            // Get the max date that streaks can be broken. This is to avoid breaking streaks while people still have time to
            // engage in that day or week (because it is the current day or week)
            var maxDateForStreakBreaking = StreakTypeService.GetMaxDateForStreakBreaking(streakTypeCache);

            // Track the streak
            var computedStreak = new ComputedStreak(minDate)
            {
                EndDate = minDate
            };

            // Define what happens for each bit in the date range
            bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion)
            {
                var iterationCanStop = false;

                // If there is an engagement, then increment the streak
                if (hasOccurrence && hasEngagement)
                {
                    computedStreak.Count++;
                    computedStreak.EndDate = currentDate;

                    // Check for a fulfilled attempt
                    if (computedStreak.Count >= numberToAchieve)
                    {
                        ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, !achievementTypeCache.AllowOverAchievement);
                        iterationCanStop = !achievementTypeCache.AllowOverAchievement;
                    }
                }
                else if (hasOccurrence && !hasEngagement && !hasExclusion)
                {
                    // Break the streak and close the attempt if there is an unexcused absence
                    ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, currentDate <= maxDateForStreakBreaking);
                    iterationCanStop = true;
                }

                // If there is a timespan and this streak is too old, then the attempt is closed
                if (attributeTimespanDays.HasValue)
                {
                    var inclusiveAge = (currentDate - computedStreak.StartDate).Days + 1;

                    if (inclusiveAge >= attributeTimespanDays.Value)
                    {
                        ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, currentDate <= maxDateForStreakBreaking);
                        iterationCanStop = true;
                    }
                }

                return(iterationCanStop);
            }

            // Iterate through the streak date for the date range specified
            streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt got an error calling StreakTypeService.IterateStreakMap: {errorMessage}");
                return;
            }

            // If the attempt wasn't closed in the iteration, then it will remain open
            if (!openAttempt.IsClosed)
            {
                var progress = CalculateProgress(computedStreak.Count, numberToAchieve);

                openAttempt.Progress     = progress;
                openAttempt.IsSuccessful = progress >= 1m;
            }
        }
예제 #22
0
        /// <summary>
        /// Update the open attempt record if there are changes.
        /// </summary>
        /// <param name="openAttempt"></param>
        /// <param name="streakTypeAchievementTypeCache">The streak type achievement type cache.</param>
        /// <param name="streak">The streak.</param>
        protected override void UpdateOpenAttempt(StreakAchievementAttempt openAttempt, StreakTypeAchievementTypeCache streakTypeAchievementTypeCache, Streak streak)
        {
            var rockContext       = new RockContext();
            var streakTypeService = new StreakTypeService(rockContext);
            var streakTypeCache   = streakTypeAchievementTypeCache.StreakTypeCache;

            // Validate the attribute values
            var numberToAccumulate = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.NumberToAccumulate).AsInteger();

            if (numberToAccumulate <= 0)
            {
                ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt cannot process because the NumberToAccumulate attribute is less than 1");
                return;
            }

            var attributeTimespanDays = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull();

            if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0)
            {
                ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt cannot process because the TimespanInDays attribute is less than 1");
                return;
            }

            // Calculate the date range where the open attempt can be validly fulfilled
            var attributeMaxDate = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.EndDateTime).AsDateTime();
            var minDate          = openAttempt.AchievementAttemptStartDateTime;
            var maxDate          = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate);

            // Track the accumulation
            var accumulation = new ComputedStreak(minDate)
            {
                EndDate = minDate
            };

            // Define what happens for each bit in the date range
            bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion)
            {
                // If there is an engagement, then increment the accumulation
                if (hasOccurrence && hasEngagement)
                {
                    accumulation.Count++;
                    accumulation.EndDate = currentDate;

                    // Check for a fulfilled attempt
                    if (accumulation.Count >= numberToAccumulate)
                    {
                        var progress = CalculateProgress(accumulation.Count, numberToAccumulate);

                        openAttempt.AchievementAttemptEndDateTime = accumulation.EndDate;
                        openAttempt.Progress     = progress;
                        openAttempt.IsClosed     = !streakTypeAchievementTypeCache.AllowOverAchievement;
                        openAttempt.IsSuccessful = progress >= 1m;
                    }
                }

                // If there is a timespan and this accumulation is too old, then the attempt is closed
                if (attributeTimespanDays.HasValue)
                {
                    var inclusiveAge = (currentDate - accumulation.StartDate).Days + 1;

                    if (inclusiveAge >= attributeTimespanDays.Value)
                    {
                        var progress = CalculateProgress(accumulation.Count, numberToAccumulate);

                        openAttempt.AchievementAttemptEndDateTime = accumulation.EndDate;
                        openAttempt.Progress     = progress;
                        openAttempt.IsClosed     = true;
                        openAttempt.IsSuccessful = progress >= 1m;
                    }
                }

                return(openAttempt.IsClosed);
            }

            // Iterate through the streak date for the date range specified
            streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage);

            if (!errorMessage.IsNullOrWhiteSpace())
            {
                ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt got an error calling StreakTypeService.IterateStreakMap: {errorMessage}");
                return;
            }

            // If the attempt wasn't closed in the iteration, then it will remain open
            if (!openAttempt.IsClosed)
            {
                var progress = CalculateProgress(accumulation.Count, numberToAccumulate);

                openAttempt.Progress     = progress;
                openAttempt.IsSuccessful = progress >= 1m;
            }
        }
예제 #23
0
        /// <summary>
        /// Logs the interactions.
        /// </summary>
        /// <param name="interactionTransactionInfos">The interaction transaction infos to process.</param>
        /// <param name="rockContext">The rock context.</param>
        private void LogInteractions(List <InteractionTransactionInfo> interactionTransactionInfos, RockContext rockContext)
        {
            List <Interaction> interactionsToInsert = new List <Interaction>();

            var interactionService = new InteractionService(rockContext);

            foreach (var info in interactionTransactionInfos.Where(a => a.InteractionComponentId.HasValue))
            {
                /*
                 * 2020-06-29 - JH
                 *
                 * The 'CreateInteraction(...)' method called below sets the following properties on the Interaction object:
                 *
                 * - InteractionComponentId
                 * - InteractionDateTime (but with the wrong value)
                 * - InteractionSessionId
                 * - Source
                 * - Medium
                 * - Campaign
                 * - Content
                 * - Term
                 */
                var interaction = interactionService.CreateInteraction(info.InteractionComponentId.Value, info.UserAgent, info.InteractionData, info.IPAddress, info.BrowserSessionId);

                // The rest of the properties need to be manually set.
                interaction.EntityId               = info.InteractionEntityId;
                interaction.Operation              = info.InteractionOperation.IsNotNullOrWhiteSpace() ? info.InteractionOperation.Trim() : "View";
                interaction.InteractionSummary     = info.InteractionSummary?.Trim();
                interaction.PersonAliasId          = info.PersonAliasId;
                interaction.InteractionDateTime    = info.InteractionDateTime;
                interaction.InteractionTimeToServe = info.InteractionTimeToServe;
                interaction.RelatedEntityTypeId    = info.InteractionRelatedEntityTypeId;
                interaction.RelatedEntityId        = info.InteractionRelatedEntityId;
                interaction.ChannelCustom1         = info.InteractionChannelCustom1?.Trim();
                interaction.ChannelCustom2         = info.InteractionChannelCustom2?.Trim();
                interaction.ChannelCustomIndexed1  = info.InteractionChannelCustomIndexed1?.Trim();
                interaction.Source                 = interaction.Source ?? info.InteractionSource?.Trim();
                interaction.Medium                 = interaction.Medium ?? info.InteractionMedium?.Trim();
                interaction.Campaign               = interaction.Campaign ?? info.InteractionCampaign?.Trim();
                interaction.Content                = interaction.Content ?? info.InteractionContent?.Trim();
                interaction.Term                   = interaction.Term ?? info.InteractionTerm?.Trim();
                interaction.InteractionLength      = info.InteractionLength;
                interaction.InteractionEndDateTime = info.InteractionEndDateTime;

                interaction.SetInteractionData(info.InteractionData?.Trim());
                interactionsToInsert.Add(interaction);
            }

            rockContext.BulkInsert(interactionsToInsert);

            // This logic is normally handled in the Interaction.PostSave method, but since the BulkInsert bypasses those
            // model hooks, streaks need to be updated here. Also, it is not necessary for this logic to complete before this
            // transaction can continue processing and exit, so update the streak using a task.

            // Only launch this task if there are StreakTypes configured that have interactions. Otherwise several
            // database calls are made only to find out there are no streak types defined.
            if (StreakTypeCache.All().Any(s => s.IsInteractionRelated))
            {
                // Ids do not exit for the interactions in the collection since they were bulk imported.
                // Read their ids from their guids and append the id.
                var insertedGuids = interactionsToInsert.Select(i => i.Guid).ToList();

                var interactionIds = new InteractionService(new RockContext()).Queryable()
                                     .Where(i => insertedGuids.Contains(i.Guid))
                                     .Select(i => new { i.Id, i.Guid })
                                     .ToList();

                foreach (var interactionId in interactionIds)
                {
                    var interaction = interactionsToInsert.Where(i => i.Guid == interactionId.Guid).FirstOrDefault();
                    if (interaction != null)
                    {
                        interaction.Id = interactionId.Id;
                    }
                }

                // Launch task
                interactionsToInsert.ForEach(i => Task.Run(() => StreakTypeService.HandleInteractionRecord(i.Id)));
            }
        }