Skip to content

Commit

Permalink
+ (Lava) Fixed the InteractionWrite block to target the correct chann…
Browse files Browse the repository at this point in the history
…el and component when specified by name. (Fixes #4602)
  • Loading branch information
MrUpsideDown committed Feb 21, 2022
1 parent c3b05db commit 1071730
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 2 deletions.
62 changes: 62 additions & 0 deletions Rock.Tests.Integration/Lava/Commands/CommandTests.cs
Expand Up @@ -15,9 +15,16 @@
// </copyright>
//

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rock.Bus.Queue;
using Rock.Data;
using Rock.Lava;
using Rock.Lava.Fluid;
using Rock.Lava.RockLiquid;
using Rock.Model;

namespace Rock.Tests.Integration.Lava
{
Expand Down Expand Up @@ -506,6 +513,61 @@ public void InteractionWriteBlock_ForEntityCommandResult_IsCached()
TestHelper.AssertTemplateOutput( expectedOutput, input, options );
}

[TestMethod]
public void InteractionWriteBlock_ForMultipleChannelInteractions_WritesInteractionsToCorrectChannels()
{
var interaction1Summary = Guid.NewGuid().ToString();
var interaction2Summary = Guid.NewGuid().ToString();
var interaction3Summary = Guid.NewGuid().ToString();
var input = @"
{% interactionwrite channeltypemediumvalueid:'1' channelname:'Channel 1' componentname:'Component 1' operation:'View' summary:'<Summary1>' %}
findme-interactiontest1
{% endinteractionwrite %}
{% interactionwrite channeltypemediumvalueid:'1' channelname:'Channel 2' componentname:'Component 2' operation:'View' summary:'<Summary2>' %}
findme-interactiontest2
{% endinteractionwrite %}
{% interactionwrite channeltypemediumvalueid:'1' channelname:'Channel 2' componentname:'Component 1' operation:'View' summary:'<Summary3>' %}
findme-interactiontest3
{% endinteractionwrite %}";
input = input
.Replace( "<Summary1>", interaction1Summary )
.Replace( "<Summary2>", interaction2Summary )
.Replace( "<Summary3>", interaction3Summary );
var expectedOutput = @"";
var options = new LavaTestRenderOptions() { EnabledCommands = "InteractionWrite" };

// Process the Lava template to create the interactions.
TestHelper.AssertTemplateOutput( typeof( FluidEngine ), expectedOutput, input, options );

// Process the transaction queue to ensure that the interactions are created.
var exceptions = new List<Exception>();
Rock.Transactions.RockQueue.Drain( ( e ) => { exceptions.Add( e ); } );
Assert.IsTrue( exceptions.Count == 0, "Interaction transaction processing failed." );

This comment has been minimized.

Copy link
@dynamiclynk

dynamiclynk Mar 2, 2022

Contributor

Should this be >0 for the test ?

This comment has been minimized.

Copy link
@MrUpsideDown

MrUpsideDown Mar 2, 2022

Author Collaborator

The Assert is verifying that no exception messages were returned from processing the transaction queue, so a 0 is what we want to see here! :-)


// Verify the interactions are assigned to the correct channel and component.
Interaction interaction;
interaction = GetInteractionBySummary( interaction1Summary );
Assert.IsTrue( interaction.InteractionComponent.InteractionChannel.Name == "Channel 1" && interaction.InteractionComponent.Name == "Component 1" );
interaction = GetInteractionBySummary( interaction2Summary );
Assert.IsTrue( interaction.InteractionComponent.InteractionChannel.Name == "Channel 2" && interaction.InteractionComponent.Name == "Component 2" );
interaction = GetInteractionBySummary( interaction3Summary );
Assert.IsTrue( interaction.InteractionComponent.InteractionChannel.Name == "Channel 2" && interaction.InteractionComponent.Name == "Component 1" );
}

private Interaction GetInteractionBySummary( string summary )
{
var rockContext = new RockContext();
var interactionService = new InteractionService( rockContext );
var interactions = interactionService
.Queryable()
.Where( x => x.InteractionSummary == summary )
.ToList();

Assert.IsTrue( interactions.Count == 1, $"Expected Interaction not found. [Interaction Summary={summary}]" );

return interactions.First();
}

#endregion

#region InteractionContentChannelItemWrite
Expand Down
19 changes: 17 additions & 2 deletions Rock/Transactions/InteractionTransaction.cs
Expand Up @@ -600,10 +600,25 @@ public void Initialize( ConcurrentQueue<InteractionTransactionInfo> queue )
// 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 );
if ( this.ChannelName != null && this.ChannelEntityId == null )
{
// If channel name is specified and entity is not, get the channel by name.
this.InteractionChannelId = InteractionChannelCache.GetOrCreateChannelIdByName( this.ChannelTypeMediumValueId.GetValueOrDefault(), this.ChannelName, this.ComponentEntityTypeId, this.InteractionEntityTypeId );
}
else
{
this.InteractionChannelId = InteractionChannelCache.GetChannelIdByTypeIdAndEntityId( this.ChannelTypeMediumValueId, this.ChannelEntityId, this.ChannelName, this.ComponentEntityTypeId, this.InteractionEntityTypeId );
}
}

this.InteractionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId( this.InteractionChannelId, this.ComponentEntityId, this.ComponentName );
if ( this.ComponentName != null && this.ComponentEntityId == null )
{
this.InteractionComponentId = InteractionComponentCache.GetOrCreateComponentIdByName( this.InteractionChannelId, this.ComponentName );
}
else
{
this.InteractionComponentId = InteractionComponentCache.GetComponentIdByChannelIdAndEntityId( this.InteractionChannelId, this.ComponentEntityId, this.ComponentName );
}

queue.Enqueue( this );
}
Expand Down
45 changes: 45 additions & 0 deletions Rock/Web/Cache/Entities/InteractionChannelCache.cs
Expand Up @@ -355,6 +355,51 @@ public static int GetChannelIdByTypeIdAndEntityId( int? channelTypeMediumValueId
}
}

/// <summary>
/// Gets the channel identifier by type identifier and name, or creates a new channel if no match is found.
/// </summary>
/// <param name="channelTypeMediumValueId">The channel type medium value identifier.</param>
/// <param name="channelName">An optional channel name. If this value is specified it will form part of the lookup for an existing channel, or used to create a new channel if no matching channel is found.</param>
/// <param name="componentEntityTypeId">The component entity type identifier. This value will only be used if a new record is created.</param>
/// <param name="interactionEntityTypeId">The interaction entity type identifier. This value will only be used if a new record is created.</param>
/// <returns></returns>
public static int GetOrCreateChannelIdByName( int channelTypeMediumValueId, string channelName, int? componentEntityTypeId, int? interactionEntityTypeId )
{
// Get a key that differentiates a name lookup, to avoid any possible collisions.
var lookupKey = $"{channelTypeMediumValueId}|name={channelName}";

if ( _interactionChannelLookupFromChannelIdByEntityId.TryGetValue( lookupKey, out int channelId ) )
{
return channelId;
}

using ( var rockContext = new RockContext() )
{
var interactionChannelService = new InteractionChannelService( rockContext );
var interactionChannel = interactionChannelService.Queryable()
.Where( a =>
a.ChannelTypeMediumValueId == channelTypeMediumValueId &&
a.Name == channelName )
.FirstOrDefault();

if ( interactionChannel == null )
{
interactionChannel = new InteractionChannel();
interactionChannel.Name = channelName;
interactionChannel.ChannelTypeMediumValueId = channelTypeMediumValueId;
interactionChannel.ComponentEntityTypeId = componentEntityTypeId;
interactionChannel.InteractionEntityTypeId = interactionEntityTypeId;
interactionChannelService.Add( interactionChannel );
rockContext.SaveChanges();
}

var interactionChannelId = Get( interactionChannel ).Id;
_interactionChannelLookupFromChannelIdByEntityId.AddOrUpdate( lookupKey, interactionChannelId, ( k, v ) => interactionChannelId );

return interactionChannelId;
}
}

/// <summary>
/// Gets the channel identifier by ForeignKey, and creates it if it doesn't exist.
/// If foreignKey is blank, this will throw a <seealso cref="ArgumentNullException" />
Expand Down
32 changes: 32 additions & 0 deletions Rock/Web/Cache/Entities/InteractionComponentCache.cs
Expand Up @@ -227,6 +227,38 @@ public static int GetComponentIdByChannelIdAndEntityId( int interactionChannelId
}
}

/// <summary>
/// Gets the component identifier by channel identifier and component name, and creates it if it doesn't exist.
/// </summary>
/// <param name="interactionChannelId">The interaction channel identifier.</param>
/// <param name="componentName">Name of the component. This value will only be used if a new record is created.</param>
/// <returns></returns>
public static int GetOrCreateComponentIdByName( int interactionChannelId, string componentName )
{
var lookupKey = $"{interactionChannelId}|name={componentName}";
if ( _interactionComponentLookupComponentIdByEntityId.TryGetValue( lookupKey, out int componentId ) )
{
return componentId;
}

using ( var rockContext = new RockContext() )
{
int? interactionComponentId = null;
var interactionComponent = new InteractionComponentService( rockContext ).GetComponentByComponentName( interactionChannelId, componentName );

// If a new component was added above we need to save the change
rockContext.SaveChanges();

if ( interactionComponent != null )
{
interactionComponentId = Get( interactionComponent ).Id;
_interactionComponentLookupComponentIdByEntityId.AddOrUpdate( lookupKey, interactionComponent.Id, ( k, v ) => interactionComponent.Id );
}

return interactionComponentId.Value;
}
}

/// <summary>
/// Gets the component identifier by foreign key and ChannelId, and creates it if it doesn't exist.
/// If foreignKey is blank, this will throw a <seealso cref="ArgumentNullException" />
Expand Down

0 comments on commit 1071730

Please sign in to comment.