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