Skip to content

Adding tracking of initialization of plugins #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions Analytics-CSharp/Segment/Analytics/Settings.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using global::System.Threading.Tasks;
using Segment.Analytics.Utilities;
using Segment.Concurrent;
Expand All @@ -15,15 +16,22 @@ public struct Settings

public partial class Analytics
{
internal void Update(Settings settings, UpdateType type) => Timeline.Apply(plugin => plugin.Update(settings, type));
internal async void Update(Settings settings) {
System systemState = await Store.CurrentState<System>();
HashSet<int> initializedPlugins = new HashSet<int>();
Timeline.Apply(plugin => {
UpdateType type = systemState._initializedPlugins.Contains(plugin.GetHashCode()) ? UpdateType.Refresh : UpdateType.Initial;
plugin.Update(settings, type);
initializedPlugins.Add(plugin.GetHashCode());
});
await Store.Dispatch<System.AddInitializedPluginAction, System>(new System.AddInitializedPluginAction(initializedPlugins));
}

private async Task CheckSettings()
internal async Task CheckSettings()
{
HTTPClient httpClient = Configuration.HttpClientProvider.CreateHTTPClient(Configuration.WriteKey, cdnHost: Configuration.CdnHost);
httpClient.AnalyticsRef = this;
System systemState = await Store.CurrentState<System>();
bool hasSettings = systemState._settings.Integrations != null && systemState._settings.Plan != null;
UpdateType updateType = hasSettings ? UpdateType.Refresh : UpdateType.Initial;

await Store.Dispatch<System.ToggleRunningAction, System>(new System.ToggleRunningAction(false));
Settings? settings = null;
Expand All @@ -41,7 +49,7 @@ await Scope.WithContext(NetworkIODispatcher, async () =>
settings = systemState._settings;
}

Update(settings.Value, updateType);
Update(settings.Value);
await Store.Dispatch<System.ToggleRunningAction, System>(new System.ToggleRunningAction(true));
}
}
Expand Down
35 changes: 29 additions & 6 deletions Analytics-CSharp/Segment/Analytics/State.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using global::System;
using Segment.Analytics.Utilities;
using Segment.Serialization;
Expand All @@ -19,13 +20,15 @@ internal struct System : IState
internal Settings _settings;
internal bool _running;
internal bool _enable;
internal HashSet<int> _initializedPlugins;

internal System(Configuration configuration, Settings settings, bool running, bool enable)
internal System(Configuration configuration, Settings settings, bool running, bool enable, HashSet<int> initializedPlugins = null)
{
_configuration = configuration;
_settings = settings;
_running = running;
_enable = enable;
_initializedPlugins = initializedPlugins ?? new HashSet<int>();
}

internal static System DefaultState(Configuration configuration, IStorage storage)
Expand All @@ -42,7 +45,7 @@ internal static System DefaultState(Configuration configuration, IStorage storag
settings = configuration.DefaultSettings;
}

return new System(configuration, settings, false, true);
return new System(configuration, settings, false, true, null);
}

internal struct UpdateSettingsAction : IAction
Expand All @@ -56,7 +59,7 @@ public IState Reduce(IState state)
IState result = null;
if (state is System systemState)
{
result = new System(systemState._configuration, _settings, systemState._running, systemState._enable);
result = new System(systemState._configuration, _settings, systemState._running, systemState._enable, systemState._initializedPlugins);
}

return result;
Expand All @@ -74,7 +77,7 @@ public IState Reduce(IState state)
IState result = null;
if (state is System systemState)
{
result = new System(systemState._configuration, systemState._settings, _running, systemState._enable);
result = new System(systemState._configuration, systemState._settings, _running, systemState._enable, systemState._initializedPlugins);
}

return result;
Expand All @@ -97,7 +100,7 @@ public IState Reduce(IState state)
Settings settings = systemState._settings;
settings.Integrations[_key] = true;

result = new System(systemState._configuration, settings, systemState._running, systemState._enable);
result = new System(systemState._configuration, settings, systemState._running, systemState._enable, systemState._initializedPlugins);
}

return result;
Expand All @@ -115,7 +118,27 @@ public IState Reduce(IState state)
IState result = null;
if (state is System systemState)
{
result = new System(systemState._configuration, systemState._settings, systemState._running, _enable);
result = new System(systemState._configuration, systemState._settings, systemState._running, _enable, systemState._initializedPlugins);
}

return result;
}
}

internal readonly struct AddInitializedPluginAction : IAction
{
private readonly HashSet<int> _pluginHashes;

public AddInitializedPluginAction(HashSet<int> pluginHashes) => _pluginHashes = pluginHashes;

public IState Reduce(IState state)
{
IState result = null;
if (state is System systemState)
{
HashSet<int> initializedPlugins = systemState._initializedPlugins;
initializedPlugins.UnionWith(_pluginHashes);
result = new System(systemState._configuration, systemState._settings, systemState._running, systemState._enable, initializedPlugins);
}

return result;
Expand Down
6 changes: 5 additions & 1 deletion Analytics-CSharp/Segment/Analytics/Timeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,12 @@ internal void Add(Plugin plugin)
analytics.AnalyticsScope.Launch(analytics.AnalyticsDispatcher, async () =>
{
Settings? settings = await plugin.Analytics.SettingsAsync();
if (settings.HasValue)
System system = await analytics.Store.CurrentState<System>();
// Don't initialize unless we have updated settings from the web.
// CheckSettings will initialize everything added before then, so wait until other inits have happened.
if (settings.HasValue && system._initializedPlugins.Count > 0)
{
await analytics.Store.Dispatch<System.AddInitializedPluginAction, System>(new System.AddInitializedPluginAction(new HashSet<int>{plugin.GetHashCode()}));
plugin.Update(settings.Value, UpdateType.Initial);
}
});
Expand Down
79 changes: 79 additions & 0 deletions Tests/Utilities/SettingsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using Segment.Analytics;
using Segment.Analytics.Utilities;
using Segment.Serialization;
using Segment.Sovran;
using Tests.Utils;
using Xunit;

namespace Tests.Utilities
{
public class SettingsTest
{
private readonly Analytics _analytics;

private Mock<IStorage> _storage;

private Settings? _settings;

public SettingsTest()
{
_settings = JsonUtility.FromJson<Settings?>(
"{\"integrations\":{\"Segment.io\":{\"apiKey\":\"1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ\"}},\"plan\":{},\"edgeFunction\":{}}");

var mockHttpClient = new Mock<HTTPClient>(null, null, null);
mockHttpClient
.Setup(httpClient => httpClient.Settings())
.ReturnsAsync(_settings);

_storage = new Mock<IStorage>();
_storage.Setup(Storage => Storage.RemoveFile("")).Returns(true);
_storage.Setup(Storage => Storage.Read(StorageConstants.Events)).Returns("test,foo");

var config = new Configuration(
writeKey: "123",
storageProvider: new MockStorageProvider(_storage),
autoAddSegmentDestination: false,
useSynchronizeDispatcher: true,
httpClientProvider: new MockHttpClientProvider(mockHttpClient)
);
_analytics = new Analytics(config);
}

[Fact]
public async Task PluginUpdatesWithInitalOnlyOnce()
{
var plugin = new Mock<DestinationPlugin>();
plugin.Setup(o => o.Key).Returns("mock");
plugin.Setup(o => o.Analytics).Returns(_analytics); // This would normally be set by Configure

// Ideally we'd interrupt init somehow to test adding plugins before, but we don't have a good way
_analytics.Add(plugin.Object);
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Initial), Times.Once);
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Refresh), Times.Never);

// load settings
await _analytics.CheckSettings();
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Initial), Times.Once);
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Refresh), Times.Once);
Segment.Analytics.System system = await _analytics.Store.CurrentState<Segment.Analytics.System>();
Assert.Contains(plugin.Object.GetHashCode(), system._initializedPlugins);

// readd plugin (why would you do this?)
_analytics.Remove(plugin.Object);
_analytics.Add(plugin.Object);
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Initial), Times.Exactly(2));
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Refresh), Times.Once);

// load settings again
await _analytics.CheckSettings();
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Initial), Times.Exactly(2));
plugin.Verify(p => p.Update(It.IsAny<Settings>(), UpdateType.Refresh), Times.Exactly(2));
}
}
}