/// <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(); }
/// <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); }
/// <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); }
/// <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); } }
/// <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); }} }}, }});"); }
/// <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); }
/// <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(); }
/// <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); } } }
/// <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()); }
/// <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); }
/// <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); }
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); }
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()); }
/// <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)); }
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")); } } } } } }
/// <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; } }
/// <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); }