Ejemplo n.º 1
0
        /// <summary>
        /// Adds the prayer interaction to the queue to be processed later.
        /// </summary>
        /// <param name="prayerRequest">The prayer request.</param>
        /// <param name="currentPerson">The current person logged in and executing the action.</param>
        /// <param name="summary">The interaction summary text.</param>
        /// <param name="userAgent">The user agent of the request.</param>
        /// <param name="ipAddress">The IP address of the request.</param>
        /// <param name="browserSessionGuid">The browser session unique identifier.</param>
        public static void EnqueuePrayerInteraction(PrayerRequest prayerRequest, Person currentPerson, string summary, string userAgent, string ipAddress, Guid?browserSessionGuid)
        {
            var channelId = InteractionChannelCache.Get(Rock.SystemGuid.InteractionChannel.PRAYER_EVENTS).Id;

            // Write the Interaction by way of a transaction.
            var info = new Rock.Transactions.InteractionTransactionInfo
            {
                InteractionChannelId = channelId,

                ComponentEntityId = prayerRequest.Id,
                ComponentName     = prayerRequest.Name,

                InteractionSummary   = summary,
                InteractionOperation = "Prayed",
                PersonAliasId        = currentPerson?.PrimaryAliasId,

                UserAgent        = userAgent,
                IPAddress        = ipAddress,
                BrowserSessionId = browserSessionGuid
            };

            var interactionTransaction = new Rock.Transactions.InteractionTransaction(info);

            interactionTransaction.Enqueue();
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Gets the channel.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="identifier">The identifier.</param>
        /// <returns></returns>
        private InteractionChannelCache GetChannel(RockContext rockContext, string identifier)
        {
            if (identifier.IsNotNullOrWhiteSpace())
            {
                // Find by Id
                int?id = identifier.AsIntegerOrNull();
                if (id.HasValue)
                {
                    var channel = InteractionChannelCache.Get(id.Value);
                    if (channel != null)
                    {
                        return(channel);
                    }
                }

                // Find by Guid
                Guid?guid = identifier.AsGuidOrNull();
                if (guid.HasValue)
                {
                    var channel = InteractionChannelCache.Get(guid.Value);
                    if (channel != null)
                    {
                        return(channel);
                    }
                }

                if (!id.HasValue && !guid.HasValue)
                {
                    // Find by Name
                    int?interactionChannelId = new InteractionChannelService(rockContext)
                                               .Queryable()
                                               .AsNoTracking()
                                               .Where(c => c.Name == identifier)
                                               .Select(c => c.Id)
                                               .Cast <int?>()
                                               .FirstOrDefault();

                    if (interactionChannelId != null)
                    {
                        return(InteractionChannelCache.Get(interactionChannelId.Value));
                    }

                    // If still no match, and we have a name, create a new channel
                    using (var newRockContext = new RockContext())
                    {
                        InteractionChannel interactionChannel = new InteractionChannel();
                        interactionChannel.Name = identifier;
                        new InteractionChannelService(newRockContext).Add(interactionChannel);
                        newRockContext.SaveChanges();
                        return(InteractionChannelCache.Get(interactionChannel.Id));
                    }
                }
            }

            return(null);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Generates the receipt.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <returns></returns>
        protected InteractionComponent GenerateReceipt(RockContext rockContext)
        {
            var interactionService = new InteractionService(rockContext);
            var receipt            = new InteractionComponent();

            new InteractionComponentService(rockContext).Add(receipt);

            receipt.ChannelId        = InteractionChannelCache.Get(com.shepherdchurch.CubeDown.SystemGuid.InteractionChannel.CUBE_DOWN_RECEIPTS.AsGuid()).Id;
            receipt.Name             = "Temporary Receipt";
            receipt.ComponentSummary = string.Format("Total {0:c}", Cart.Total);

            if (Customer != null)
            {
                receipt.EntityId = Customer.PrimaryAliasId;
            }

            foreach (var item in Cart.Items)
            {
                var interaction = new Interaction
                {
                    EntityId            = null,
                    Operation           = "Buy",
                    InteractionDateTime = RockDateTime.Now,
                    InteractionData     = item.ToJson()
                };

                if (item.Quantity == 1)
                {
                    interaction.InteractionSummary = item.Name;
                }
                else
                {
                    interaction.InteractionSummary = string.Format("{0} (qty {1})", item.Name, item.Quantity);
                }

                interactionService.Add(interaction);
            }

            rockContext.SaveChanges();

            receipt = new InteractionComponentService(rockContext).Get(receipt.Id);

            var receiptCode = ReceiptHelper.EncodeReceiptCode(receipt);

            Cart.ReceiptCode = receiptCode;

            receipt.Name          = string.Format("Receipt #{0}", receiptCode);
            receipt.ComponentData = Cart.ToJson();

            rockContext.SaveChanges();


            return(receipt);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Gets the component by entity identifier, and creates it if it doesn't exist
        /// </summary>
        /// <param name="channelGuid">The channel unique identifier.</param>
        /// <param name="entityId">The entity identifier.</param>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public InteractionComponent GetComponentByEntityId(Guid channelGuid, int entityId, string name)
        {
            var channel = InteractionChannelCache.Get(channelGuid);

            if (channel != null)
            {
                return(GetComponentByChannelIdAndEntityId(channel.Id, entityId, name));
            }
            else
            {
                return(null);
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Gets the java script.
        /// </summary>
        /// <param name="badge"></param>
        /// <returns></returns>
        protected override string GetJavaScript(BadgeCache badge)
        {
            if (Person == null)
            {
                return(null);
            }

            var interactionChannelGuid = GetAttributeValue(badge, "InteractionChannel").AsGuidOrNull();
            var badgeColor             = GetAttributeValue(badge, "BadgeColor");

            if (!interactionChannelGuid.HasValue || string.IsNullOrEmpty(badgeColor))
            {
                return(null);
            }

            var dateRange = GetAttributeValue(badge, "DateRange");
            var badgeIcon = GetAttributeValue(badge, "BadgeIconCss");

            var pageId             = PageCache.Get(GetAttributeValue(badge, "DetailPage").AsGuid())?.Id;
            var interactionChannel = InteractionChannelCache.Get(interactionChannelGuid.Value);
            var detailPageUrl      = VirtualPathUtility.ToAbsolute($"~/page/{pageId}?ChannelId={interactionChannel?.Id}");

            return($@"
                $.ajax({{
                    type: 'GET',
                    url: Rock.settings.get('baseUrl') + 'api/Badges/InteractionsInRange/{Person.Id}/{interactionChannel.Id}/{HttpUtility.UrlEncode( dateRange )}' ,
                    statusCode: {{
                        200: function (data, status, xhr) {{

                        var interactionCount = data;
                        var opacity = 0;
                        if(data===0){{
                            opacity = 0.4;
                        }} else {{
                            opacity = 1;
                        }}
                        var linkUrl = '{detailPageUrl}';

                        if (linkUrl != '') {{
                                badgeContent = '<a href=\'' + linkUrl + '\'><span class=\'badge-content fa-layers fa-fw\' style=\'opacity:'+ opacity +'\'><i class=\'fa {badgeIcon} badge-icon\' style=\'color: {badgeColor}\'></i><span class=\'fa-layers-counter\'>'+ interactionCount +'</span></span></a>';
                            }} else {{
                                badgeContent = '<div class=\'badge-content fa-layers \' style=\'opacity:'+ opacity +'\'><i class=\'fa {badgeIcon} badge-icon\' style=\'color: {badgeColor}\'></i><span class=\'fa-layers-counter\'>'+ interactionCount +'</span></div>';
                            }}

                            $('.badge-interactioninrange.badge-id-{badge.Id}').html(badgeContent);
                        }}
                    }},
                }});");
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Creates the control(s) necessary for prompting user for a new value
        /// </summary>
        /// <param name="configurationValues">The configuration values.</param>
        /// <param name="id"></param>
        /// <returns>
        /// The control
        /// </returns>
        public override Control EditControl(Dictionary <string, ConfigurationValue> configurationValues, string id)
        {
            var editControl = new InteractionChannelInteractionComponentPicker {
                ID = id
            };

            if (configurationValues != null && configurationValues.ContainsKey(ConfigKey.DefaultInteractionChannelGuid))
            {
                var channelGuid = configurationValues[ConfigKey.DefaultInteractionChannelGuid].Value.AsGuidOrNull();

                if (channelGuid.HasValue)
                {
                    var channel = InteractionChannelCache.Get(channelGuid.Value);
                    editControl.DefaultInteractionChannelId = channel?.Id;
                }
            }

            return(editControl);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Method that will be called on an entity immediately after the item is saved by context
        /// </summary>
        /// <param name="dbContext">The database context.</param>
        public override void PostSaveChanges(Data.DbContext dbContext)
        {
            if (this.SaveState == EntityState.Added || this.SaveState == EntityState.Deleted)
            {
                var channel = InteractionChannelCache.Get(this.InteractionChannelId);
                if (channel != null)
                {
                    if (this.SaveState == EntityState.Added)
                    {
                        channel.AddComponentId(this.Id);
                    }
                    else
                    {
                        channel.RemoveComponentId(this.Id);
                    }
                }
            }

            base.PostSaveChanges(dbContext);
        }
            /// <summary>
            /// Called after the save operation has been executed
            /// </summary>
            /// <remarks>
            /// This method is only called if <see cref="M:Rock.Data.EntitySaveHook`1.PreSave" /> returns
            /// without error.
            /// </remarks>
            protected override void PostSave()
            {
                if (this.PreSaveState == EntityContextState.Added || this.PreSaveState == EntityContextState.Deleted)
                {
                    var channel = InteractionChannelCache.Get(Entity.InteractionChannelId);
                    if (channel != null)
                    {
                        if (this.PreSaveState == EntityContextState.Added)
                        {
                            channel.AddComponentId(Entity.Id);
                        }
                        else
                        {
                            channel.RemoveComponentId(Entity.Id);
                        }
                    }
                }

                base.PostSave();
            }
Ejemplo n.º 9
0
        /// <summary>
        /// Gets the models from the delimited values.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="interactionChannel">The interactionChannel</param>
        /// <param name="interactionComponent">The interactionComponent</param>
        private void GetModelsFromAttributeValue(string value, out InteractionChannelCache interactionChannel, out InteractionComponentCache interactionComponent)
        {
            interactionChannel   = null;
            interactionComponent = null;

            ParseDelimitedGuids(value, out var interactionChannelGuid, out var interactionComponentGuid);

            if (interactionChannelGuid.HasValue || interactionComponentGuid.HasValue)
            {
                if (interactionChannelGuid.HasValue)
                {
                    interactionChannel = InteractionChannelCache.Get(interactionChannelGuid.Value);
                }

                if (interactionComponentGuid.HasValue)
                {
                    interactionComponent = InteractionComponentCache.Get(interactionComponentGuid.Value);
                }
            }
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Gets the last prayed details for a collection of prayer requests.
        /// </summary>
        /// <param name="prayerRequestIds">The prayer request identifiers.</param>
        /// <returns>A collection of <see cref="PrayerRequestLastPrayedDetail"/> that describe the most recent prayer interactions.</returns>
        public IEnumerable <PrayerRequestLastPrayedDetail> GetLastPrayedDetails(IEnumerable <int> prayerRequestIds)
        {
            var prayerRequestInteractionChannel = InteractionChannelCache.Get(Rock.SystemGuid.InteractionChannel.PRAYER_EVENTS).Id;

            return(new InteractionService(( RockContext )Context).Queryable()
                   .Where(i =>
                          i.InteractionComponent.EntityId.HasValue &&
                          prayerRequestIds.Contains(i.InteractionComponent.EntityId.Value) &&
                          i.InteractionComponent.InteractionChannelId == prayerRequestInteractionChannel &&
                          i.Operation == "Prayed")
                   .GroupBy(i => i.InteractionComponentId)
                   .Select(i => i.OrderByDescending(x => x.InteractionDateTime).FirstOrDefault())
                   .Select(y => new PrayerRequestLastPrayedDetail
            {
                RequestId = y.InteractionComponent.EntityId.Value,
                PrayerDateTime = y.InteractionDateTime,
                FirstName = y.PersonAlias.Person.NickName,
                LastName = y.PersonAlias.Person.LastName
            })
                   .ToList());
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Reads new values entered by the user for the field ( as Guid )
        /// </summary>
        /// <param name="control">Parent control that controls were added to in the CreateEditControl() method</param>
        /// <param name="configurationValues">The configuration values.</param>
        /// <returns></returns>
        public override string GetEditValue(Control control, Dictionary <string, ConfigurationValue> configurationValues)
        {
            var interactionChannelInteractionComponentPicker = control as InteractionChannelInteractionComponentPicker;

            if (interactionChannelInteractionComponentPicker != null)
            {
                var  rockContext              = new RockContext();
                Guid?interactionChannelGuid   = null;
                Guid?interactionComponentGuid = null;

                if (interactionChannelInteractionComponentPicker.InteractionChannelId.HasValue)
                {
                    var channel = InteractionChannelCache.Get(interactionChannelInteractionComponentPicker.InteractionChannelId.Value);

                    if (channel != null)
                    {
                        interactionChannelGuid = channel.Guid;
                    }
                }

                if (interactionChannelInteractionComponentPicker.InteractionComponentId.HasValue)
                {
                    var component = InteractionComponentCache.Get(interactionChannelInteractionComponentPicker.InteractionComponentId.Value);

                    if (component != null)
                    {
                        interactionComponentGuid = component.Guid;
                    }
                }

                if (interactionChannelGuid.HasValue || interactionComponentGuid.HasValue)
                {
                    return(string.Format("{0}|{1}", interactionChannelGuid, interactionComponentGuid));
                }
            }

            return(null);
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Further configure this info object based on the options specified by the caller, then enqueue it to be bulk inserted.
        /// </summary>
        /// <param name="queue">The <see cref="ConcurrentQueue{InterationInfo}"/> into which this info object should be enqueued.</param>
        public void Initialize(ConcurrentQueue <InteractionTransactionInfo> queue)
        {
            RockPage    rockPage = null;
            HttpRequest request  = null;

            if (this.GetValuesFromHttpRequest)
            {
                try
                {
                    rockPage = HttpContext.Current.Handler as RockPage;
                }
                catch
                {
                    rockPage = null;
                }

                try
                {
                    if (rockPage != null)
                    {
                        request = rockPage.Request;
                    }
                    else if (HttpContext.Current != null)
                    {
                        request = HttpContext.Current.Request;
                    }
                }
                catch
                {
                    // Intentionally ignore exception (.Request will throw an exception instead of simply returning null if it isn't available).
                }
            }

            // Fall back to values from the HTTP request if specified by the caller AND the values weren't explicitly set on the info object:
            // (The rockPage and request variables will only be defined if this.GetValuesFromHttpRequest was true.)

            this.InteractionData = this.InteractionData ?? request?.Url.ToString();
            this.UserAgent       = this.UserAgent ?? request?.UserAgent;

            try
            {
                this.IPAddress = this.IPAddress ?? RockPage.GetClientIpAddress();
            }
            catch
            {
                this.IPAddress = string.Empty;
            }

            this.BrowserSessionId = this.BrowserSessionId ?? rockPage?.Session["RockSessionId"]?.ToString().AsGuidOrNull();

            this.PersonAliasId = this.PersonAliasId ?? rockPage?.CurrentPersonAliasId;

            // Make sure we don't exceed this field's character limit.
            this.InteractionOperation = EnforceLengthLimitation(this.InteractionOperation, 25);

            if (this.InteractionSummary.IsNullOrWhiteSpace())
            {
                // If InteractionSummary was not specified, use the Page title.
                var title = string.Empty;
                if (rockPage != null)
                {
                    if (rockPage.BrowserTitle.IsNotNullOrWhiteSpace())
                    {
                        title = rockPage.BrowserTitle;
                    }
                    else
                    {
                        title = rockPage.PageTitle;
                    }
                }

                // Remove the site name from the title.
                if (title?.Contains("|") == true)
                {
                    title = title.Substring(0, title.LastIndexOf('|')).Trim();
                }

                this.InteractionSummary = title;
            }

            // Make sure we don't exceed these fields' character limit.
            this.InteractionSummary               = EnforceLengthLimitation(this.InteractionSummary, 500);
            this.InteractionChannelCustom1        = EnforceLengthLimitation(this.InteractionChannelCustom1, 500);
            this.InteractionChannelCustom2        = EnforceLengthLimitation(this.InteractionChannelCustom2, 2000);
            this.InteractionChannelCustomIndexed1 = EnforceLengthLimitation(this.InteractionChannelCustomIndexed1, 500);
            this.InteractionSource   = EnforceLengthLimitation(this.InteractionSource, 25);
            this.InteractionMedium   = EnforceLengthLimitation(this.InteractionMedium, 25);
            this.InteractionCampaign = EnforceLengthLimitation(this.InteractionCampaign, 50);
            this.InteractionContent  = EnforceLengthLimitation(this.InteractionContent, 50);
            this.InteractionTerm     = EnforceLengthLimitation(this.InteractionTerm, 50);

            // Get existing (or create new) interaction channel and interaction component for this interaction.
            if (this.InteractionChannelId == default)
            {
                this.InteractionChannelId = InteractionChannelCache.GetChannelIdByTypeIdAndEntityId(this.ChannelTypeMediumValueId, this.ChannelEntityId, this.ChannelName, this.ComponentEntityTypeId, this.InteractionEntityTypeId);
            }

            this.InteractionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(this.InteractionChannelId, this.ComponentEntityId, this.ComponentName);

            queue.Enqueue(this);
        }
 /// <summary>
 /// Updates any Cache Objects that are associated with this entity
 /// </summary>
 /// <param name="entityState">State of the entity.</param>
 /// <param name="dbContext">The database context.</param>
 public void UpdateCache(EntityState entityState, Rock.Data.DbContext dbContext)
 {
     InteractionChannelCache.UpdateCachedEntity(this.Id, entityState);
 }
Ejemplo n.º 14
0
        public MediaElementInteraction PostWatchInteraction(MediaElementInteraction mediaInteraction)
        {
            var rockContext        = Service.Context as RockContext;
            var personService      = new PersonService(rockContext);
            var personAliasService = new PersonAliasService(rockContext);
            var interactionService = new InteractionService(rockContext);
            int?personAliasId;

            if (!IsWatchMapValid(mediaInteraction.WatchMap))
            {
                var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"The WatchMap contains invalid characters.");
                throw new HttpResponseException(errorResponse);
            }

            // Get the person alias to associate with the interaction in
            // order of provided Person.Guid, then PersonAlias.Guid, then
            // the logged in Person.
            if (mediaInteraction.PersonGuid.HasValue)
            {
                personAliasId = personAliasService.GetPrimaryAliasId(mediaInteraction.PersonGuid.Value);
            }
            else if (mediaInteraction.PersonAliasGuid.HasValue)
            {
                personAliasId = personAliasService.GetId(mediaInteraction.PersonAliasGuid.Value);
            }
            else
            {
                personAliasId = GetPersonAliasId(rockContext);
            }

            var mediaElement = new MediaElementService(rockContext).GetNoTracking(mediaInteraction.MediaElementGuid);

            // Ensure we have our required MediaElement.
            if (mediaElement == null)
            {
                var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"The MediaElement could not be found.");
                throw new HttpResponseException(errorResponse);
            }

            // Get (or create) the component.
            var         interactionChannelId   = InteractionChannelCache.Get(SystemGuid.InteractionChannel.MEDIA_EVENTS).Id;
            var         interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId, mediaElement.Id, mediaElement.Name);
            Interaction interaction            = null;

            if (mediaInteraction.InteractionGuid.HasValue)
            {
                // Look for an existing Interaction by it's Guid and the
                // component it is supposed to show up under. But also
                // check that the RelatedEntityTypeId/RelatedEntityId values
                // are either null or match what was passed by the user.
                // Finally also make sure the Interaction Person Alias is
                // either the one for this user OR if there is a Person
                // record attached to that alias that the alias Id we have is
                // also attached to that Person record OR the interaction is
                // not tied to a person.
                interaction = interactionService.Queryable()
                              .Where(a => a.Guid == mediaInteraction.InteractionGuid.Value && a.InteractionComponentId == interactionComponentId)
                              .Where(a => !a.RelatedEntityTypeId.HasValue || !mediaInteraction.RelatedEntityTypeId.HasValue || a.RelatedEntityTypeId == mediaInteraction.RelatedEntityTypeId)
                              .Where(a => !a.RelatedEntityId.HasValue || !mediaInteraction.RelatedEntityId.HasValue || a.RelatedEntityId == mediaInteraction.RelatedEntityId)
                              .Where(a => !a.PersonAliasId.HasValue || a.PersonAliasId == personAliasId || a.PersonAlias.Person.Aliases.Any(b => b.Id == personAliasId))
                              .SingleOrDefault();
            }

            var watchedPercentage = CalculateWatchedPercentage(mediaInteraction.WatchMap);

            if (interaction != null)
            {
                // Update the interaction data with the new watch map.
                var data = interaction.InteractionData.FromJsonOrNull <MediaWatchedInteractionData>() ?? new MediaWatchedInteractionData();
                data.WatchMap          = mediaInteraction.WatchMap;
                data.WatchedPercentage = watchedPercentage;

                interaction.InteractionData        = data.ToJson();
                interaction.InteractionLength      = watchedPercentage;
                interaction.InteractionEndDateTime = RockDateTime.Now;
                interaction.PersonAliasId          = interaction.PersonAliasId ?? personAliasId;

                if (mediaInteraction.RelatedEntityTypeId.HasValue)
                {
                    interaction.RelatedEntityTypeId = mediaInteraction.RelatedEntityTypeId;
                }

                if (mediaInteraction.RelatedEntityId.HasValue)
                {
                    interaction.RelatedEntityId = mediaInteraction.RelatedEntityId;
                }
            }
            else
            {
                // Generate the interaction data from the watch map.
                var data = new MediaWatchedInteractionData
                {
                    WatchMap          = mediaInteraction.WatchMap,
                    WatchedPercentage = watchedPercentage
                };

                interaction = interactionService.CreateInteraction(interactionComponentId,
                                                                   null, "Watch", string.Empty, data.ToJson(), personAliasId, RockDateTime.Now,
                                                                   null, null, null, null, null, null);

                interaction.InteractionLength      = watchedPercentage;
                interaction.InteractionEndDateTime = RockDateTime.Now;
                interaction.RelatedEntityTypeId    = mediaInteraction.RelatedEntityTypeId;
                interaction.RelatedEntityId        = mediaInteraction.RelatedEntityId;

                interactionService.Add(interaction);
            }

            rockContext.SaveChanges();

            mediaInteraction.InteractionGuid = interaction.Guid;

            return(mediaInteraction);
        }
Ejemplo n.º 15
0
        public IHttpActionResult PostInteractions([FromBody] List <MobileInteractionSession> sessions, Guid?personalDeviceGuid = null)
        {
            var person    = GetPerson();
            var ipAddress = System.Web.HttpContext.Current?.Request?.UserHostAddress;

            using (var rockContext = new Data.RockContext())
            {
                var interactionChannelService   = new InteractionChannelService(rockContext);
                var interactionComponentService = new InteractionComponentService(rockContext);
                var interactionSessionService   = new InteractionSessionService(rockContext);
                var interactionService          = new InteractionService(rockContext);
                var userLoginService            = new UserLoginService(rockContext);
                var channelMediumTypeValue      = DefinedValueCache.Get(SystemGuid.DefinedValue.INTERACTIONCHANNELTYPE_WEBSITE);
                var pageEntityTypeId            = EntityTypeCache.Get(typeof(Model.Page)).Id;

                //
                // Check to see if we have a site and the API key is valid.
                //
                if (MobileHelper.GetCurrentApplicationSite() == null)
                {
                    return(StatusCode(System.Net.HttpStatusCode.Forbidden));
                }

                //
                // Get the personal device identifier if they provided it's unique identifier.
                //
                int?personalDeviceId = null;
                if (personalDeviceGuid.HasValue)
                {
                    personalDeviceId = new PersonalDeviceService(rockContext).GetId(personalDeviceGuid.Value);
                }

                //
                // Create a quick way to cache data since we have to loop twice.
                //
                var interactionComponentLookup = new Dictionary <string, int>();

                //
                // Helper method to get a cache key for looking up the component Id.
                //
                string GetComponentCacheKey(MobileInteraction mi)
                {
                    return($"{mi.AppId}:{mi.PageGuid}:{mi.ChannelGuid}:{mi.ChannelId}:{mi.ComponentId}:{mi.ComponentName}");
                }

                //
                // Interactions Components will now try to load from cache which
                // causes problems if we are inside a transaction. So first loop through
                // everything and make sure all our components and channels exist.
                //
                var prePassInteractions = sessions.SelectMany(a => a.Interactions)
                                          .DistinctBy(a => GetComponentCacheKey(a));

                //
                // It's safe to do this pre-pass outside the transaction since we are just creating
                // the channels and components (if necessary), which is going to have to be done at
                // at some point no matter what.
                //
                foreach (var mobileInteraction in prePassInteractions)
                {
                    //
                    // Lookup the interaction channel, and create it if it doesn't exist
                    //
                    if (mobileInteraction.AppId.HasValue && mobileInteraction.PageGuid.HasValue)
                    {
                        var site = SiteCache.Get(mobileInteraction.AppId.Value);
                        var page = PageCache.Get(mobileInteraction.PageGuid.Value);

                        if (site == null || page == null)
                        {
                            continue;
                        }

                        //
                        // Try to find an existing interaction channel.
                        //
                        var interactionChannelId = InteractionChannelCache.GetChannelIdByTypeIdAndEntityId(channelMediumTypeValue.Id, site.Id, site.Name, pageEntityTypeId, null);

                        //
                        // Get an existing or create a new component.
                        //
                        var interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId, page.Id, page.InternalName);

                        interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), interactionComponentId);
                    }
                    else if (mobileInteraction.ChannelId.HasValue || mobileInteraction.ChannelGuid.HasValue)
                    {
                        int?interactionChannelId = null;

                        if (mobileInteraction.ChannelId.HasValue)
                        {
                            interactionChannelId = mobileInteraction.ChannelId.Value;
                        }
                        else if (mobileInteraction.ChannelGuid.HasValue)
                        {
                            interactionChannelId = InteractionChannelCache.Get(mobileInteraction.ChannelGuid.Value)?.Id;
                        }

                        if (interactionChannelId.HasValue)
                        {
                            if (mobileInteraction.ComponentId.HasValue)
                            {
                                // Use the provided component identifier.
                                interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), mobileInteraction.ComponentId.Value);
                            }
                            else if (mobileInteraction.ComponentName.IsNotNullOrWhiteSpace())
                            {
                                int interactionComponentId;

                                // Get or create a new component with the details we have.
                                if (mobileInteraction.ComponentEntityId.HasValue)
                                {
                                    interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId.Value, mobileInteraction.ComponentEntityId, mobileInteraction.ComponentName);
                                }
                                else
                                {
                                    var interactionComponent = interactionComponentService.GetComponentByComponentName(interactionChannelId.Value, mobileInteraction.ComponentName);

                                    rockContext.SaveChanges();

                                    interactionComponentId = interactionComponent.Id;
                                }

                                interactionComponentLookup.AddOrReplace(GetComponentCacheKey(mobileInteraction), interactionComponentId);
                            }
                        }
                    }
                }

                //
                // Now wrap the actual interaction creation inside a transaction. We should
                // probably move this so it uses the InteractionTransaction class for better
                // performance. This is so we can inform the client that either everything
                // saved or that nothing saved. No partial saves here.
                //
                rockContext.WrapTransaction(() =>
                {
                    foreach (var mobileSession in sessions)
                    {
                        var interactionGuids         = mobileSession.Interactions.Select(i => i.Guid).ToList();
                        var existingInteractionGuids = interactionService.Queryable()
                                                       .Where(i => interactionGuids.Contains(i.Guid))
                                                       .Select(i => i.Guid)
                                                       .ToList();

                        //
                        // Loop through all interactions that don't already exist and add each one.
                        //
                        foreach (var mobileInteraction in mobileSession.Interactions.Where(i => !existingInteractionGuids.Contains(i.Guid)))
                        {
                            string cacheKey = GetComponentCacheKey(mobileInteraction);

                            if (!interactionComponentLookup.ContainsKey(cacheKey))
                            {
                                // Shouldn't happen, but just in case.
                                continue;
                            }

                            var interactionComponentId = interactionComponentLookup[cacheKey];

                            //
                            // Add the interaction
                            //
                            var interaction = interactionService.CreateInteraction(interactionComponentId,
                                                                                   mobileInteraction.EntityId,
                                                                                   mobileInteraction.Operation,
                                                                                   mobileInteraction.Summary,
                                                                                   mobileInteraction.Data,
                                                                                   person?.PrimaryAliasId,
                                                                                   RockDateTime.ConvertLocalDateTimeToRockDateTime(mobileInteraction.DateTime.LocalDateTime),
                                                                                   mobileSession.Application,
                                                                                   mobileSession.OperatingSystem,
                                                                                   mobileSession.ClientType,
                                                                                   null,
                                                                                   ipAddress,
                                                                                   mobileSession.Guid);

                            interaction.Guid                  = mobileInteraction.Guid;
                            interaction.PersonalDeviceId      = personalDeviceId;
                            interaction.RelatedEntityTypeId   = mobileInteraction.RelatedEntityTypeId;
                            interaction.RelatedEntityId       = mobileInteraction.RelatedEntityId;
                            interaction.ChannelCustom1        = mobileInteraction.ChannelCustom1;
                            interaction.ChannelCustom2        = mobileInteraction.ChannelCustom2;
                            interaction.ChannelCustomIndexed1 = mobileInteraction.ChannelCustomIndexed1;

                            interactionService.Add(interaction);

                            // Attempt to process this as a communication interaction.
                            ProcessCommunicationInteraction(mobileSession, mobileInteraction, rockContext);
                        }
                    }

                    rockContext.SaveChanges();
                });
            }

            return(Ok());
        }
Ejemplo n.º 16
0
        /// <summary>
        /// Gets the component.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="channel">The channel.</param>
        /// <param name="entityId">The entity identifier.</param>
        /// <param name="identifier">The identifier.</param>
        /// <returns></returns>
        private InteractionComponentCache GetComponent(RockContext rockContext, InteractionChannelCache channel, int?entityId, string identifier)
        {
            if (channel != null)
            {
                if (entityId.HasValue)
                {
                    // Find by the Entity Id
                    int?interactionComponentId = new InteractionComponentService(rockContext)
                                                 .Queryable()
                                                 .AsNoTracking()
                                                 .Where(c => c.EntityId.HasValue && c.InteractionChannelId == channel.Id && c.EntityId.Value == entityId.Value)
                                                 .Select(c => c.Id)
                                                 .Cast <int?>()
                                                 .FirstOrDefault();

                    if (interactionComponentId != null)
                    {
                        return(InteractionComponentCache.Get(interactionComponentId.Value));
                    }
                }

                if (identifier.IsNotNullOrWhiteSpace())
                {
                    // Find by Id
                    int?id = identifier.AsIntegerOrNull();
                    if (id.HasValue)
                    {
                        var component = InteractionComponentCache.Get(id.Value);
                        if (component != null && component.InteractionChannelId == channel.Id)
                        {
                            return(component);
                        }
                    }

                    // Find by Guid
                    Guid?guid = identifier.AsGuidOrNull();
                    if (guid.HasValue)
                    {
                        var component = InteractionComponentCache.Get(guid.Value);
                        if (component != null && component.InteractionChannelId == channel.Id)
                        {
                            return(component);
                        }
                    }

                    if (!id.HasValue && !guid.HasValue)
                    {
                        // Find by Name
                        int?interactionComponentId = new InteractionComponentService(rockContext)
                                                     .Queryable()
                                                     .AsNoTracking()
                                                     .Where(c => c.InteractionChannelId == channel.Id)
                                                     .Where(c => c.Name.Equals(identifier, StringComparison.OrdinalIgnoreCase))
                                                     .Select(c => c.Id)
                                                     .Cast <int?>()
                                                     .FirstOrDefault();

                        if (interactionComponentId != null)
                        {
                            return(InteractionComponentCache.Get(interactionComponentId.Value));
                        }

                        // If still no match, and we have a name, create a new channel
                        using (var newRockContext = new RockContext())
                        {
                            var interactionComponent = new InteractionComponent();
                            interactionComponent.Name = identifier;
                            interactionComponent.InteractionChannelId = channel.Id;
                            new InteractionComponentService(newRockContext).Add(interactionComponent);
                            newRockContext.SaveChanges();

                            return(InteractionComponentCache.Get(interactionComponent.Id));
                        }
                    }
                }
            }

            return(null);
        }
 /// <summary>
 /// Gets the cache object associated with this Entity
 /// </summary>
 /// <returns></returns>
 public IEntityCache GetCacheObject()
 {
     return(InteractionChannelCache.Get(this.Id));
 }
Ejemplo n.º 18
0
        public MediaElementInteraction GetWatchInteraction([FromUri] Guid?mediaElementGuid = null, [FromUri] Guid?personGuid = null, Guid?personAliasGuid = null)
        {
            var rockContext        = Service.Context as RockContext;
            var personService      = new PersonService(rockContext);
            var personAliasService = new PersonAliasService(rockContext);
            var interactionService = new InteractionService(rockContext);
            int?personAliasId;

            // Get the person alias to associate with the interaction in
            // order of provided Person.Guid, then PersonAlias.Guid, then
            // the logged in Person.
            if (personGuid.HasValue)
            {
                personAliasId = personAliasService.GetPrimaryAliasId(personGuid.Value);
            }
            else if (personAliasGuid.HasValue)
            {
                personAliasId = personAliasService.GetId(personAliasGuid.Value);
            }
            else
            {
                personAliasId = GetPersonAliasId(rockContext);
            }

            // Verify we have a person alias, otherwise bail out.
            if (!personAliasId.HasValue)
            {
                var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"The personAliasId could not be determined.");
                throw new HttpResponseException(errorResponse);
            }

            MediaElement mediaElement = null;

            // In the future we might make MediaElementGuid optional so
            // perform the check this way rather than making it required
            // in the parameter list.
            if (mediaElementGuid.HasValue)
            {
                mediaElement = new MediaElementService(rockContext).GetNoTracking(mediaElementGuid.Value);
            }

            // Ensure we have our required MediaElement.
            if (mediaElement == null)
            {
                var errorResponse = Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"The MediaElement could not be found.");
                throw new HttpResponseException(errorResponse);
            }

            // Get (or create) the component.
            var interactionChannelId   = InteractionChannelCache.Get(SystemGuid.InteractionChannel.MEDIA_EVENTS).Id;
            var interactionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId(interactionChannelId, mediaElement.Id, mediaElement.Name);

            Interaction interaction = interactionService.Queryable()
                                      .AsNoTracking()
                                      .Include(a => a.PersonAlias)
                                      .Where(a => a.InteractionComponentId == interactionComponentId)
                                      .Where(a => a.PersonAliasId == personAliasId || a.PersonAlias.Person.Aliases.Any(b => b.Id == personAliasId))
                                      .OrderByDescending(a => a.InteractionEndDateTime)
                                      .ThenByDescending(a => a.InteractionDateTime)
                                      .FirstOrDefault();

            if (interaction == null)
            {
                var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, $"The Interaction could not be found.");
                throw new HttpResponseException(errorResponse);
            }

            var data = interaction.InteractionData.FromJsonOrNull <MediaWatchedInteractionData>();

            return(new MediaElementInteraction
            {
                InteractionGuid = interaction.Guid,
                MediaElementGuid = mediaElement.Guid,
                PersonGuid = interaction.PersonAlias.Person?.Guid,
                PersonAliasGuid = interaction.PersonAlias.Guid,
                RelatedEntityTypeId = interaction.RelatedEntityTypeId,
                RelatedEntityId = interaction.RelatedEntityId,
                WatchMap = data?.WatchMap ?? string.Empty
            });
        }
        public void Execute(IJobExecutionContext context)
        {
            JobDataMap dataMap     = context.JobDetail.JobDataMap;
            var        rockContext = new RockContext();

            InteractionChannelService   channelService              = new InteractionChannelService(rockContext);
            InteractionComponentService componentService            = new InteractionComponentService(rockContext);
            InteractionService          interactionService          = new InteractionService(rockContext);
            ScheduleService             scheduleService             = new ScheduleService(rockContext);
            LocationService             locationService             = new LocationService(rockContext);
            AttendanceService           attendanceService           = new AttendanceService(rockContext);
            AttendanceOccurrenceService attendanceOccurrenceService = new AttendanceOccurrenceService(rockContext);
            GroupService groupService = new GroupService(rockContext);

            // Load the channel
            InteractionChannelCache channel = InteractionChannelCache.Get(dataMap.GetString("InteractionChannel").AsGuid());

            // Setup
            int    campusLocationTypeId = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.LOCATION_TYPE_CAMPUS).Id;
            var    groupType            = GroupTypeCache.Get(dataMap.GetString("GroupType").AsGuid());
            var    groupLocations       = groupService.GetByGroupTypeId(groupType.Id).Where(g => g.IsActive == true).SelectMany(g => g.GroupLocations).ToList();
            string operation            = !string.IsNullOrWhiteSpace(dataMap.GetString("Operation")) ? dataMap.GetString("Operation") : null;

            // Fetch the job so we can get the last run date/time
            int jobId      = Convert.ToInt16(context.JobDetail.Description);
            var jobService = new ServiceJobService(rockContext);
            var job        = jobService.Get(jobId);

            DateTime  lastRun = job?.LastSuccessfulRunDateTime ?? DateTime.MinValue;
            var       componentCampusMapping = dataMap.GetString("ComponentCampusMapping").AsDictionaryOrNull();
            DateRange dateRange = Rock.Web.UI.Controls.SlidingDateRangePicker.CalculateDateRangeFromDelimitedValues(dataMap.GetString("DateRange") ?? "-1||");

            // Flip the component campus mapping around and translate to ids
            Dictionary <int, List <int> > campusComponentIds = new Dictionary <int, List <int> >();

            foreach (CampusCache campus in CampusCache.All())
            {
                var componentNames = componentCampusMapping.Where(ccm => ccm.Value == campus.Name).Select(c => c.Key.ToLower());
                campusComponentIds[campus.Id] = componentService.Queryable().Where(cs => componentNames.Contains(cs.Name.ToLower()) && cs.ChannelId == channel.Id).Select(c => c.Id).ToList();
            }

            foreach (GroupLocation gl in groupLocations)
            {
                if (gl.Group.CampusId.HasValue)
                {
                    Location   location     = gl.Location;
                    List <int> componentIds = campusComponentIds[gl.Group.CampusId.Value];

                    foreach (Schedule schedule in gl.Schedules)
                    {
                        var occurrences = schedule.GetOccurrences(dateRange.Start.Value, dateRange.End.Value);
                        foreach (var occurrence in occurrences)
                        {
                            DateTime startDate = occurrence.Period.StartTime.Value;
                            DateTime endDate   = occurrence.Period.EndTime.Value;

                            var peopleAttended = interactionService.Queryable().Where(
                                i => componentIds.Contains(i.InteractionComponentId) &&
                                i.InteractionDateTime <= endDate &&
                                i.InteractionEndDateTime >= startDate &&
                                i.PersonAliasId != null &&
                                (i.CreatedDateTime > lastRun || i.PersonalDevice.ModifiedDateTime > lastRun || i.PersonalDevice.CreatedDateTime > lastRun) &&
                                (operation == null || i.Operation == operation)
                                ).Select(i => i.PersonAliasId).Distinct();
                            int newAttendance = 0;

                            var occurrenceModel = attendanceOccurrenceService.Get(occurrence.Period.StartTime.Value.Date, gl.GroupId, location.Id, schedule.Id);

                            // Make sure we don't already have an attendance Record
                            var existingAttendees = attendanceOccurrenceService.Queryable().Where(ao => DbFunctions.TruncateTime(ao.OccurrenceDate) == occurrence.Period.StartTime.Value.Date && ao.ScheduleId == schedule.Id && ao.GroupId == gl.GroupId && ao.LocationId == location.Id).SelectMany(a => a.Attendees).Where(a => a.DidAttend == true).Select(a => a.PersonAliasId);
                            foreach (int personAliasId in peopleAttended.Except(existingAttendees))
                            {
                                // Check to see if an occurrence exists already
                                if (occurrenceModel == null)
                                {
                                    var attendance = attendanceService.AddOrUpdate(personAliasId, occurrence.Period.StartTime.Value, gl.GroupId, location.Id, schedule.Id, gl.Group.CampusId);

                                    attendance.EndDateTime = occurrence.Period?.EndTime?.Value;
                                    attendance.DidAttend   = true;
                                    attendance.CampusId    = gl.Group.CampusId;
                                    occurrenceModel        = attendance.Occurrence;
                                }
                                else
                                {
                                    Attendance attendance = new Attendance();
                                    attendance.PersonAliasId = personAliasId;
                                    attendance.OccurrenceId  = occurrenceModel.Id;
                                    attendance.StartDateTime = occurrence.Period.StartTime.Value;
                                    attendance.EndDateTime   = occurrence.Period?.EndTime?.Value;
                                    attendance.DidAttend     = true;
                                    attendance.CampusId      = gl.Group.CampusId;
                                    attendanceService.Add(attendance);
                                }

                                newAttendance++;
                            }
                            if (newAttendance > 0)
                            {
                                rockContext.SaveChanges();
                                context.Result += string.Format("{0} people attended {1} on {2}.\n", newAttendance, gl.Group.Campus.Name, occurrence.Period.StartTime.Value.ToString("MM/dd/yyyy h:mm tt"));
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 20
0
        /// <summary>
        /// Shows the charts.
        /// </summary>
        public void ShowCharts()
        {
            SetDateRangeUI();

            var rockContext = new RockContext();

            hfCommunicationId.Value          = this.PageParameter("CommunicationId");
            hfCommunicationListGroupId.Value = this.PageParameter("CommunicationListId");

            int?   communicationId          = hfCommunicationId.Value.AsIntegerOrNull();
            string noDataMessageName        = string.Empty;
            int?   communicationListGroupId = hfCommunicationListGroupId.Value.AsIntegerOrNull();
            int?   maxMonthsBack            = null;

            if (communicationId.HasValue)
            {
                // specific communication specified
                var communication = new CommunicationService(rockContext).Get(communicationId.Value);
                if (communication != null)
                {
                    lTitle.Text       = "Email Analytics: " + (communication.Name ?? communication.Subject);
                    noDataMessageName = communication.Name ?? communication.Subject;
                }
                else
                {
                    // Invalid Communication specified
                    nbCommunicationorCommunicationListFound.Visible = true;
                    nbCommunicationorCommunicationListFound.Text    = "Invalid communication specified";
                }
            }
            else if (communicationListGroupId.HasValue)
            {
                // specific communicationgroup specified
                var communicationListGroup = new GroupService(rockContext).Get(communicationListGroupId.Value);
                if (communicationListGroup != null)
                {
                    lTitle.Text       = "Email Analytics: " + communicationListGroup.Name;
                    noDataMessageName = communicationListGroup.Name;
                }
                else
                {
                    nbCommunicationorCommunicationListFound.Visible = true;
                    nbCommunicationorCommunicationListFound.Text    = "Invalid communication list group specified";
                }
            }
            else
            {
                // no specific communication or list specific, so just show overall stats,
                // but limit to a date range so that we don't impact performance or show years worth of data
                pnlSelectedMonthsDateRange.Visible = true;
                maxMonthsBack = hfSelectedMonthsDateRange.Value.AsIntegerOrNull() ?? 12;
                lTitle.Text   = "Email Analytics";
            }

            var interactionChannelCommunicationId = InteractionChannelCache.GetId(Rock.SystemGuid.InteractionChannel.COMMUNICATION.AsGuid());

            if (!interactionChannelCommunicationId.HasValue)
            {
                nbConfigurationError.Text = "Rock.SystemGuid.InteractionChannel.COMMUNICATION not found in database";
                return;
            }

            var interactionQuery = new InteractionService(rockContext).Queryable().Where(a => a.EntityId.HasValue).Where(a => a.InteractionComponent.InteractionChannelId == interactionChannelCommunicationId.Value);

            if (maxMonthsBack.HasValue)
            {
                var startDate = RockDateTime.Today.AddMonths(-maxMonthsBack.Value);

                interactionQuery = interactionQuery.Where(a => a.InteractionDateTime >= startDate);
            }

            List <int> communicationIdList = null;

            if (communicationId.HasValue)
            {
                communicationIdList = new List <int>();
                communicationIdList.Add(communicationId.Value);
            }
            else if (communicationListGroupId.HasValue)
            {
                communicationIdList = new CommunicationService(rockContext).Queryable().Where(a => a.ListGroupId == communicationListGroupId).Select(a => a.Id).ToList();
            }

            if (communicationIdList != null)
            {
                interactionQuery = interactionQuery.Where(a => communicationIdList.Contains(a.InteractionComponent.EntityId.Value));
            }

            var interactionsList = interactionQuery
                                   .Select(a => new
            {
                a.InteractionDateTime,
                a.Operation,
                a.InteractionData,
                CommunicationRecipientId = a.EntityId.Value,
                InteractionSessionDeviceTypeClientType  = a.InteractionSession.DeviceType.ClientType,
                InteractionSessionDeviceTypeApplication = a.InteractionSession.DeviceType.Application
            })
                                   .ToList();

            TimeSpan roundTimeSpan = TimeSpan.FromDays(1);

            this.SeriesColorsJSON    = this.GetAttributeValue(AttributeKey.SeriesColors).SplitDelimitedValues().ToArray().ToJson();
            this.LineChartTimeFormat = "LL";

            if (interactionsList.Any())
            {
                var firstDateTime = interactionsList.Min(a => a.InteractionDateTime);
                var lastDateTime  = interactionsList.Max(a => a.InteractionDateTime);
                var weeksCount    = (lastDateTime - firstDateTime).TotalDays / 7;

                if (weeksCount > 26)
                {
                    // if there is more than 26 weeks worth, summarize by week
                    roundTimeSpan = TimeSpan.FromDays(7);
                }
                else if (weeksCount > 3)
                {
                    // if there is between 3 and 26 weeks of data, summarize by day
                    roundTimeSpan = TimeSpan.FromDays(1);
                }
                else
                {
                    // if there is less than 3 weeks worth, summarize by hour
                    roundTimeSpan            = TimeSpan.FromHours(1);
                    this.LineChartTimeFormat = "LLLL";
                }
            }

            List <SummaryInfo> interactionsSummary = new List <SummaryInfo>();

            interactionsSummary = interactionsList.GroupBy(a => new { a.CommunicationRecipientId, a.Operation })
                                  .Select(a => new
            {
                InteractionSummaryDateTime = a.Min(b => b.InteractionDateTime).Round(roundTimeSpan),
                a.Key.CommunicationRecipientId,
                a.Key.Operation
            })
                                  .GroupBy(a => a.InteractionSummaryDateTime)
                                  .Select(x => new SummaryInfo
            {
                SummaryDateTime = x.Key,
                ClickCounts     = x.Count(xx => xx.Operation == "Click"),
                OpenCounts      = x.Count(xx => xx.Operation == "Opened")
            }).OrderBy(a => a.SummaryDateTime).ToList();

            var lineChartHasData = interactionsSummary.Any();

            openClicksLineChartCanvas.Style[HtmlTextWriterStyle.Display] = lineChartHasData ? string.Empty : "none";
            nbOpenClicksLineChartMessage.Visible = !lineChartHasData;
            nbOpenClicksLineChartMessage.Text    = "No communications activity" + (!string.IsNullOrEmpty(noDataMessageName) ? " for " + noDataMessageName : string.Empty);

            hfLineChartDataLabelsJSON.Value = "[" + interactionsSummary.Select(a => "new Date('" + a.SummaryDateTime.ToString("o") + "')").ToList().AsDelimited(",\n") + "]";

            List <int> cumulativeClicksList = new List <int>();
            List <int> clickCountsList      = interactionsSummary.Select(a => a.ClickCounts).ToList();
            int        clickCountsSoFar     = 0;

            foreach (var clickCounts in clickCountsList)
            {
                clickCountsSoFar += clickCounts;
                cumulativeClicksList.Add(clickCountsSoFar);
            }

            hfLineChartDataClicksJSON.Value = cumulativeClicksList.ToJson();

            List <int> cumulativeOpensList = new List <int>();
            List <int> openCountsList      = interactionsSummary.Select(a => a.OpenCounts).ToList();
            int        openCountsSoFar     = 0;

            foreach (var openCounts in openCountsList)
            {
                openCountsSoFar += openCounts;
                cumulativeOpensList.Add(openCountsSoFar);
            }

            hfLineChartDataOpensJSON.Value = cumulativeOpensList.ToJson();

            int?deliveredRecipientCount = null;
            int?failedRecipientCount    = null;

            if (communicationIdList != null)
            {
                CommunicationRecipientStatus[] sentStatus   = new CommunicationRecipientStatus[] { CommunicationRecipientStatus.Opened, CommunicationRecipientStatus.Delivered };
                CommunicationRecipientStatus[] failedStatus = new CommunicationRecipientStatus[] { CommunicationRecipientStatus.Failed };
                deliveredRecipientCount = new CommunicationRecipientService(rockContext).Queryable().Where(a => sentStatus.Contains(a.Status) && communicationIdList.Contains(a.CommunicationId)).Count();
                failedRecipientCount    = new CommunicationRecipientService(rockContext).Queryable().Where(a => failedStatus.Contains(a.Status) && communicationIdList.Contains(a.CommunicationId)).Count();
            }

            List <int> unopenedCountsList = new List <int>();

            if (deliveredRecipientCount.HasValue)
            {
                int unopenedRemaining = deliveredRecipientCount.Value;
                foreach (var openCounts in openCountsList)
                {
                    unopenedRemaining = unopenedRemaining - openCounts;

                    // NOTE: just in case we have more recipients activity then there are recipient records, don't let it go negative
                    unopenedCountsList.Add(Math.Max(unopenedRemaining, 0));
                }

                hfLineChartDataUnOpenedJSON.Value = unopenedCountsList.ToJson();
            }
            else
            {
                hfLineChartDataUnOpenedJSON.Value = "null";
            }

            /* Actions Pie Chart and Stats */
            int totalOpens  = interactionsList.Where(a => a.Operation == "Opened").Count();
            int totalClicks = interactionsList.Where(a => a.Operation == "Click").Count();

            // Unique Opens is the number of times a Recipient opened at least once
            int uniqueOpens = interactionsList.Where(a => a.Operation == "Opened").GroupBy(a => a.CommunicationRecipientId).Count();

            // Unique Clicks is the number of times a Recipient clicked at least once in an email
            int uniqueClicks = interactionsList.Where(a => a.Operation == "Click").GroupBy(a => a.CommunicationRecipientId).Count();

            string actionsStatFormatNumber  = "<span class='{0}'>{1}</span><br><span class='js-actions-statistic' title='{2}' style='font-size: 45px; font-weight: 700; line-height: 40px;'>{3:#,##0}</span>";
            string actionsStatFormatPercent = "<span class='{0}'>{1}</span><br><span class='js-actions-statistic' title='{2}' style='font-size: 45px; font-weight: 700; line-height: 40px;'>{3:P2}</span>";

            if (deliveredRecipientCount.HasValue)
            {
                lDelivered.Text        = string.Format(actionsStatFormatNumber, "label label-default", "Delivered", "The number of recipients that the email was successfully delivered to", deliveredRecipientCount);
                lPercentOpened.Text    = string.Format(actionsStatFormatPercent, "label label-opened", "Percent Opened", "The percent of the delivered emails that were opened at least once", deliveredRecipientCount > 0 ? ( decimal )uniqueOpens / deliveredRecipientCount : 0);
                lFailedRecipients.Text = string.Format(actionsStatFormatNumber, "label label-danger", "Failed Recipients", "The number of emails that failed to get delivered", failedRecipientCount);

                // just in case there are more opens then delivered, don't let it go negative
                var unopenedCount = Math.Max(deliveredRecipientCount.Value - uniqueOpens, 0);
                lUnopened.Text = string.Format(actionsStatFormatNumber, "label label-unopened", "Unopened", "The number of emails that were delivered but not yet opened", unopenedCount);
            }

            lUniqueOpens.Text = string.Format(actionsStatFormatNumber, "label label-opened", "Unique Opens", "The number of emails that were opened at least once", uniqueOpens);
            lTotalOpens.Text  = string.Format(actionsStatFormatNumber, "label label-opened", "Total Opens", "The total number of times the emails were opened, including ones that were already opened once", totalOpens);

            lUniqueClicks.Text = string.Format(actionsStatFormatNumber, "label label-clicked", "Unique Clicks", "The number of times a recipient clicked on a link at least once in any of the opened emails", uniqueClicks);
            lTotalClicks.Text  = string.Format(actionsStatFormatNumber, "label label-clicked", "Total Clicks", "The total number of times a link was clicked in any of the opened emails", totalClicks);

            if (uniqueOpens > 0)
            {
                lClickThroughRate.Text = string.Format(actionsStatFormatPercent, "label label-clicked", "Click Through Rate (CTR)", "The percent of emails that had at least one click", ( decimal )uniqueClicks / uniqueOpens);
            }
            else
            {
                lClickThroughRate.Text = string.Empty;
            }

            // action stats is [opens,clicks,unopened];
            var actionsStats = new int?[3];

            // "Opens" would be unique number that Clicked Or Opened, so subtract clicks so they aren't counted twice
            actionsStats[0] = uniqueOpens - uniqueClicks;
            actionsStats[1] = uniqueClicks;

            if (deliveredRecipientCount.HasValue)
            {
                // NOTE: just in case we have more recipients activity then there are recipient records, don't let it go negative
                actionsStats[2] = Math.Max(deliveredRecipientCount.Value - uniqueOpens, 0);
            }
            else
            {
                actionsStats[2] = null;
            }

            hfPieChartDataOpenClicksJSON.Value = actionsStats.ToJson();

            var pieChartOpenClicksHasData = actionsStats.Sum() > 0;

            opensClicksPieChartCanvas.Style[HtmlTextWriterStyle.Display] = pieChartOpenClicksHasData ? string.Empty : "none";
            nbOpenClicksPieChartMessage.Visible = !pieChartOpenClicksHasData;
            nbOpenClicksPieChartMessage.Text    = "No communications activity" + (!string.IsNullOrEmpty(noDataMessageName) ? " for " + noDataMessageName : string.Empty);

            int interactionCount = interactionsList.Count();

            /* Clients-In-Use (Client Type) Pie Chart*/
            var clientsUsageByClientType = interactionsList
                                           .GroupBy(a => (a.InteractionSessionDeviceTypeClientType ?? "Unknown").ToLower()).Select(a => new ClientTypeUsageInfo
            {
                ClientType   = a.Key,
                UsagePercent = a.Count() * 100.00M / interactionCount
            }).OrderByDescending(a => a.UsagePercent).ToList()
                                           .Where(a => !a.ClientType.Equals("Robot", StringComparison.OrdinalIgnoreCase)) // no robots
                                           .Select(a => new ClientTypeUsageInfo
            {
                ClientType   = a.ClientType,
                UsagePercent = Math.Round(a.UsagePercent, 2)
            }).ToList();

            hfPieChartDataClientLabelsJSON.Value = clientsUsageByClientType.Select(a => string.IsNullOrEmpty(a.ClientType) ? "Unknown" : a.ClientType.Transform(To.TitleCase)).ToList().ToJson();
            hfPieChartDataClientCountsJSON.Value = clientsUsageByClientType.Select(a => a.UsagePercent).ToList().ToJson();

            var clientUsageHasData = clientsUsageByClientType.Where(a => a.UsagePercent > 0).Any();

            clientsDoughnutChartCanvas.Style[HtmlTextWriterStyle.Display] = clientUsageHasData ? string.Empty : "none";
            nbClientsDoughnutChartMessage.Visible = !clientUsageHasData;
            nbClientsDoughnutChartMessage.Text    = "No client usage activity" + (!string.IsNullOrEmpty(noDataMessageName) ? " for " + noDataMessageName : string.Empty);

            /* Clients-In-Use (Application) Grid */
            var clientsUsageByApplication = interactionsList
                                            .GroupBy(a => a.InteractionSessionDeviceTypeApplication).Select(a => new ApplicationUsageInfo
            {
                Application  = a.Key,
                UsagePercent = (a.Count() * 100.00M / interactionCount)
            }).OrderByDescending(a => a.UsagePercent).ToList();

            pnlClientApplicationUsage.Visible    = clientsUsageByApplication.Any();
            rptClientApplicationUsage.DataSource = clientsUsageByApplication;
            rptClientApplicationUsage.DataBind();

            /* Most Popular Links from Clicks*/
            var topClicks = interactionsList
                            .Where(a =>
                                   a.Operation == "Click" &&
                                   !string.IsNullOrEmpty(a.InteractionData) &&
                                   !a.InteractionData.Contains("/Unsubscribe/"))
                            .GroupBy(a => a.InteractionData)
                            .Select(a => new
            {
                LinkUrl          = a.Key,
                UniqueClickCount = a.GroupBy(x => x.CommunicationRecipientId).Count()
            })
                            .OrderByDescending(a => a.UniqueClickCount)
                            .Take(100)
                            .ToList();

            if (topClicks.Any())
            {
                int topLinkCount = topClicks.Max(a => a.UniqueClickCount);

                // CTR really only makes sense if we are showing data for a single communication, so only show if it's a single communication
                bool singleCommunication = communicationIdList != null && communicationIdList.Count == 1;

                var mostPopularLinksData = topClicks.Select(a => new TopLinksInfo
                {
                    PercentOfTop = ( decimal )a.UniqueClickCount * 100 / topLinkCount,
                    Url          = a.LinkUrl,
                    UniquesCount = a.UniqueClickCount,
                    CTRPercent   = singleCommunication ? a.UniqueClickCount * 100.00M / deliveredRecipientCount : ( decimal? )null
                }).ToList();

                pnlCTRHeader.Visible = singleCommunication;

                rptMostPopularLinks.DataSource = mostPopularLinksData;
                rptMostPopularLinks.DataBind();
                pnlMostPopularLinks.Visible = true;
            }
            else
            {
                pnlMostPopularLinks.Visible = false;
            }
        }
Ejemplo n.º 21
0
        /// <summary>
        /// BulkInserts Interaction Records
        /// </summary>
        /// <remarks>
        /// If any PersonAliasId references a PersonAliasId record that doesn't exist, the field value will be set to null.
        /// Also, if the InteractionComponent Id (or Guid) is specified, but references a Interaction Component record that doesn't exist
        /// the Interaction will not be recorded.
        /// </remarks>
        /// <param name="interactionsImport">The interactions import.</param>
        internal static void BulkInteractionImport(InteractionsImport interactionsImport)
        {
            if (interactionsImport == null)
            {
                throw new Exception("InteractionsImport must be assigned a value.");
            }

            var interactionImportList = interactionsImport.Interactions;

            if (interactionImportList == null || !interactionImportList.Any())
            {
                // if there aren't any return
                return;
            }

            /* 2020-05-14 MDP
             * Make sure that all the PersonAliasIds in the import exist in the database.
             * For performance reasons, look them up all at one and keep a list of valid ones.
             *
             * If there are any PersonAliasIds that aren't valid,
             * we decided that just set the PersonAliasId to null (we want ignore bad data).
             */

            HashSet <int> validPersonAliasIds = interactionsImport.GetValidPersonAliasIds();

            List <Interaction> interactionsToInsert = new List <Interaction>();

            foreach (InteractionImport interactionImport in interactionImportList)
            {
                if (interactionImport.Interaction == null)
                {
                    throw new ArgumentNullException("InteractionImport.Interaction can not be null");
                }

                // Determine which Channel this should be set to
                if (interactionImport.InteractionChannelId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionChannelId = InteractionChannelCache.Get(interactionImport.InteractionChannelId.Value)?.Id;
                }

                // Determine which Channel Type Medium this should be set to
                if (interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.Get(interactionImport.InteractionChannelChannelTypeMediumValueId.Value)?.Id;
                }

                if (!interactionImport.InteractionChannelChannelTypeMediumValueId.HasValue)
                {
                    if (interactionImport.InteractionChannelChannelTypeMediumValueGuid.HasValue)
                    {
                        interactionImport.InteractionChannelChannelTypeMediumValueId = DefinedValueCache.GetId(interactionImport.InteractionChannelChannelTypeMediumValueGuid.Value);
                    }
                }

                if (!interactionImport.InteractionChannelId.HasValue)
                {
                    if (interactionImport.InteractionChannelGuid.HasValue)
                    {
                        interactionImport.InteractionChannelId = InteractionChannelCache.GetId(interactionImport.InteractionChannelGuid.Value);
                    }

                    // if InteractionChannelId is still null, lookup (or create) an InteractionChannel from InteractionChannelForeignKey (if it is specified)
                    if (interactionImport.InteractionChannelId == null && interactionImport.InteractionChannelForeignKey.IsNotNullOrWhiteSpace())
                    {
                        interactionImport.InteractionChannelId = InteractionChannelCache.GetCreateChannelIdByForeignKey(interactionImport.InteractionChannelForeignKey, interactionImport.InteractionChannelName, interactionImport.InteractionChannelChannelTypeMediumValueId);
                    }
                    else
                    {
                        /* 2020-05-14 MDP
                         *  Discussed this and decided that if we tried InteractionChannelId and InteractionChannelGuid, and InteractionChannelForeignKey was not specified,
                         *  we'll just skip over this record
                         */
                        continue;
                    }
                }

                // Determine which Component this should be set to
                if (interactionImport.InteractionComponentId.HasValue)
                {
                    // make sure it is a valid Id
                    interactionImport.InteractionComponentId = InteractionComponentCache.Get(interactionImport.InteractionComponentId.Value)?.Id;
                }

                if (!interactionImport.InteractionComponentId.HasValue)
                {
                    if (interactionImport.InteractionComponentGuid.HasValue)
                    {
                        interactionImport.InteractionComponentId = InteractionComponentCache.GetId(interactionImport.InteractionComponentGuid.Value);
                    }

                    // if InteractionComponentId is still null, lookup (or create) an InteractionComponent from the ForeignKey and ChannelId
                    if (interactionImport.InteractionComponentForeignKey.IsNotNullOrWhiteSpace())
                    {
                        interactionImport.InteractionComponentId = InteractionComponentCache.GetComponentIdByForeignKeyAndChannelId(
                            interactionImport.InteractionComponentForeignKey,
                            interactionImport.InteractionChannelId.Value,
                            interactionImport.InteractionComponentName);
                    }
                    else
                    {
                        /* 2020-05-14 MDP
                         *  Discussed this and decided that and if we tried InteractionComponentId and InteractionComponentGuid, and InteractionComponentForeignKey was not specified,
                         *  we'll just skip over this record
                         */
                        continue;
                    }
                }
            }

            foreach (InteractionImport interactionImport in interactionImportList.Where(a => a.InteractionComponentId.HasValue))
            {
                Interaction interaction = new Interaction
                {
                    InteractionComponentId = interactionImport.InteractionComponentId.Value
                };

                interaction.InteractionDateTime = interactionImport.Interaction.InteractionDateTime;

                // if operation is over 25, truncate it
                interaction.Operation = interactionImport.Interaction.Operation.Truncate(25);

                interaction.InteractionComponentId = interactionImport.InteractionComponentId.Value;
                interaction.EntityId = interactionImport.Interaction.EntityId;
                if (interactionImport.Interaction.RelatedEntityTypeId.HasValue)
                {
                    /* 2020-05-14 MDP
                     * We want to ignore bad data, so first see if the RelatedEntityTypeId exists by looking it up in a cache.
                     * If it doesn't exist, it'll set RelatedEntityTypeId to null (so that we don't get a database constraint error)
                     */

                    interaction.RelatedEntityTypeId = EntityTypeCache.Get(interactionImport.Interaction.RelatedEntityTypeId.Value)?.Id;
                }

                interaction.RelatedEntityId = interactionImport.Interaction.RelatedEntityId;

                if (interactionImport.Interaction.PersonAliasId.HasValue)
                {
                    /* 2020-05-14 MDP
                     * We want to ignore bad data, so see if the specified PersonAliasId exists in the validPersonAliasIds that we lookup up
                     * If it doesn't exist, we'll leave interaction.PersonAliasId null (so that we don't get a database constraint error)
                     */

                    if (validPersonAliasIds.Contains(interactionImport.Interaction.PersonAliasId.Value))
                    {
                        interaction.PersonAliasId = interactionImport.Interaction.PersonAliasId.Value;
                    }
                }

                // BulkImport doesn't include Session information TODO???
                interaction.InteractionSessionId = null;

                // if the summary is over 500 chars, truncate with addEllipsis=true
                interaction.InteractionSummary = interactionImport.Interaction.InteractionSummary.Truncate(500, true);

                interaction.InteractionData  = interactionImport.Interaction.InteractionData;
                interaction.PersonalDeviceId = interactionImport.Interaction.PersonalDeviceId;

                interaction.InteractionEndDateTime = interactionImport.Interaction.InteractionEndDateTime;

                // Campaign related fields, we'll truncate those if they are too long
                interaction.Source      = interactionImport.Interaction.Source.Truncate(25);
                interaction.Medium      = interactionImport.Interaction.Medium.Truncate(25);
                interaction.Campaign    = interactionImport.Interaction.Campaign.Truncate(50);
                interaction.Content     = interactionImport.Interaction.Content.Truncate(50);
                interaction.Term        = interactionImport.Interaction.Term.Truncate(50);
                interaction.ForeignId   = interactionImport.Interaction.ForeignId;
                interaction.ForeignKey  = interactionImport.Interaction.ForeignKey;
                interaction.ForeignGuid = interactionImport.Interaction.ForeignGuid;

                interaction.ChannelCustom1         = interactionImport.Interaction.ChannelCustom1.Truncate(500, true);
                interaction.ChannelCustom2         = interactionImport.Interaction.ChannelCustom2.Truncate(2000, true);
                interaction.ChannelCustomIndexed1  = interactionImport.Interaction.ChannelCustomIndexed1.Truncate(500, true);
                interaction.InteractionLength      = interactionImport.Interaction.InteractionLength;
                interaction.InteractionTimeToServe = interactionImport.Interaction.InteractionTimeToServe;

                interactionsToInsert.Add(interaction);
            }

            using (var rockContext = new RockContext())
            {
                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)));
            }
        }
        /// <summary>
        /// Renders the specified writer.
        /// </summary>
        /// <param name="badge">The badge.</param>
        /// <param name="writer">The writer.</param>
        public override void Render(PersonBadgeCache badge, System.Web.UI.HtmlTextWriter writer)
        {
            Guid?  interactionChannelGuid = GetAttributeValue(badge, "InteractionChannel").AsGuid();
            string badgeColor             = GetAttributeValue(badge, "BadgeColor");

            if (interactionChannelGuid.HasValue && !String.IsNullOrEmpty(badgeColor))
            {
                string dateRange = GetAttributeValue(badge, "DateRange");
                string badgeIcon = GetAttributeValue(badge, "BadgeIconCss");

                var interactionChannel = InteractionChannelCache.Get(interactionChannelGuid.Value);

                string detailPageUrl = string.Empty;

                if (!String.IsNullOrEmpty(GetAttributeValue(badge, "DetailPage")))
                {
                    int pageId = PageCache.Get(Guid.Parse(GetAttributeValue(badge, "DetailPage"))).Id;

                    detailPageUrl = System.Web.VirtualPathUtility.ToAbsolute($"~/page/{pageId}?ChannelId={interactionChannel.Id}");
                }


                writer.Write($"<div class='badge badge-interactioninrange badge-id-{badge.Id} fa-3x' data-toggle='tooltip' data-original-title=''>");

                writer.Write("</div>");

                writer.Write($@"
                <script>
                    Sys.Application.add_load(function () {{
                                                
                        $.ajax({{
                                type: 'GET',
                                url: Rock.settings.get('baseUrl') + 'api/PersonBadges/InteractionsInRange/{Person.Id}/{interactionChannel.Id}/{HttpUtility.UrlEncode(dateRange)}' ,
                                statusCode: {{
                                    200: function (data, status, xhr) {{
                                    
                                var interactionCount = data;
                                var opacity = 0;
                                if(data===0){{
                                    opacity = 0.4;
                                }} else {{
                                    opacity = 1;    
                                }}
                                var linkUrl = '{detailPageUrl}';

                                 if (linkUrl != '') {{
                                                badgeContent = '<a href=\'' + linkUrl + '\'><span class=\'badge-content fa-layers fa-fw\' style=\'opacity:'+ opacity +'\'><i class=\'fas {badgeIcon} badge-icon\' style=\'color: {badgeColor}\'></i><span class=\'fa-layers-counter\'>'+ interactionCount +'</span></span></a>';
                                            }} else {{
                                                badgeContent = '<div class=\'badge-content fa-layers \' style=\'opacity:'+ opacity +'\'><i class=\'fas {badgeIcon} badge-icon\' style=\'color: {badgeColor}\'></i><span class=\'fa-layers-counter\'>'+ interactionCount +'</span></div>';
                                            }}

                                            $('.badge-interactioninrange.badge-id-{badge.Id}').html(badgeContent);
                                        }}
                                }},
                        }});
                    }});
                </script>
                
            ");
            }
        }
        public void Execute(IJobExecutionContext context)
        {
            JobDataMap dataMap     = context.JobDetail.JobDataMap;
            var        rockContext = new RockContext();

            InteractionChannelService   channelService     = new InteractionChannelService(rockContext);
            InteractionComponentService componentService   = new InteractionComponentService(rockContext);
            InteractionService          interactionService = new InteractionService(rockContext);
            ScheduleService             scheduleService    = new ScheduleService(rockContext);
            LocationService             locationService    = new LocationService(rockContext);
            AttendanceService           attendanceService  = new AttendanceService(rockContext);
            GroupService groupService = new GroupService(rockContext);

            // Load the channel
            InteractionChannelCache channel = InteractionChannelCache.Read(dataMap.GetString("InteractionChannel").AsGuid());

            // Setup
            int    campusLocationTypeId = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.LOCATION_TYPE_CAMPUS).Id;
            var    groupType            = GroupTypeCache.Read(dataMap.GetString("GroupType").AsGuid());
            var    groups    = groupService.GetByGroupTypeId(groupType.Id);
            string operation = !string.IsNullOrWhiteSpace(dataMap.GetString("Operation")) ? dataMap.GetString("Operation") : null;

            // Fetch the job so we can get the last run date/time
            int jobId      = Convert.ToInt16(context.JobDetail.Description);
            var jobService = new ServiceJobService(rockContext);
            var job        = jobService.Get(jobId);

            DateTime lastRun = job?.LastSuccessfulRunDateTime ?? DateTime.MinValue;

            var componentCampusMapping = dataMap.GetString("ComponentCampusMapping").AsDictionaryOrNull();

            foreach (var componentName in componentCampusMapping.Keys)
            {
                var         component = componentService.Queryable().Where(cs => cs.Name.ToLower() == componentName.ToLower() && cs.ChannelId == channel.Id).FirstOrDefault();
                CampusCache campus    = CampusCache.All().Where(c => c.Name == componentCampusMapping[componentName]).FirstOrDefault();

                if (campus != null && component != null)
                {
                    Group group = groups.Where(g => g.IsActive == true && g.CampusId == campus.Id).FirstOrDefault();
                    if (group?.GroupLocations != null)
                    {
                        foreach (var gl in group?.GroupLocations)
                        {
                            Location location = gl.Location;
                            foreach (Schedule schedule in gl.Schedules)
                            {
                                var occurrences = schedule.GetOccurrences(DateTime.MinValue, DateTime.Now);
                                foreach (var occurrence in occurrences)
                                {
                                    DateTime startDate = occurrence.Period.StartTime.Value;
                                    DateTime endDate   = occurrence.Period.EndTime.Value;

                                    var peopleAttended = interactionService.Queryable().Where(
                                        i => i.InteractionComponentId == component.Id &&
                                        i.InteractionDateTime <= endDate &&
                                        i.InteractionEndDateTime >= startDate &&
                                        i.PersonAliasId != null &&
                                        (i.CreatedDateTime > lastRun || i.PersonalDevice.ModifiedDateTime > lastRun || i.PersonalDevice.CreatedDateTime > lastRun) &&
                                        (operation == null || i.Operation == operation)
                                        ).Select(i => i.PersonAliasId).Distinct();
                                    int newAttendance = 0;
                                    foreach (int personAliasId in peopleAttended)
                                    {
                                        // Make sure we don't already have an attendance Record
                                        if (!attendanceService.Queryable().Any(a => DbFunctions.TruncateTime(a.StartDateTime) == occurrence.Period.StartTime.Value.Date && a.ScheduleId == schedule.Id && a.PersonAliasId == personAliasId && a.GroupId == group.Id && a.LocationId == location.Id && a.DidAttend == true))
                                        {
                                            Attendance attendance = new Attendance()
                                            {
                                                PersonAliasId = personAliasId,
                                                CampusId      = campus.Id,
                                                GroupId       = group.Id,
                                                LocationId    = location.Id,
                                                ScheduleId    = schedule.Id,
                                                StartDateTime = occurrence.Period.StartTime.Value,
                                                EndDateTime   = occurrence.Period?.EndTime?.Value,
                                                DidAttend     = true
                                            };
                                            attendanceService.Add(attendance);
                                            newAttendance++;
                                        }
                                    }
                                    if (newAttendance > 0)
                                    {
                                        rockContext.SaveChanges();
                                        context.Result += string.Format("{0} people attended {1} on {2} (Component {3}).\n", newAttendance, campus.Name, occurrence.Period.StartTime.Value.ToString("MM/dd/yyyy h:mm tt"), component.Name);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Gets the interaction channel cache.
        /// </summary>
        /// <param name="achievementTypeCache">The achievement type cache.</param>
        /// <returns></returns>
        private InteractionChannelCache GetInteractionChannelCache(AchievementTypeCache achievementTypeCache)
        {
            var guid = GetInteractionChannelGuid(achievementTypeCache);

            return(guid.HasValue ? InteractionChannelCache.Get(guid.Value) : null);
        }