diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.UI.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.UI.cs index 5b0a81c48..9df2e7bd6 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.UI.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.UI.cs @@ -365,7 +365,7 @@ private void RegisterHandlers() if (label.ClassListContains(SampleAppUi.Css.Copied)) return; if (string.IsNullOrEmpty(label.text) || label.text == SampleAppUi.StatusBar.EmptyText) return; GUIUtility.systemCopyBuffer = label.text; - label.text = "Copied!"; + label.text = SampleAppUi.ButtonText.CopiedFlash; FlashCopied(label); }); } @@ -533,17 +533,17 @@ private void RefreshStatusBar() string? derivedFromKey = keyEmpty ? null : (isTest ? Constants.SandboxBaseUrl : Constants.ProductionBaseUrl); string? endpoint = hasOverride ? overrideUrl : derivedFromKey; bool warnState = hasOverride || (!keyEmpty && !isTest); - SetStatusCell(_statusEndpoint, endpoint, warnState ? "state-warn" : "state-ok"); + SetStatusCell(_statusEndpoint, endpoint, warnState ? SampleAppUi.Css.StateWarn : SampleAppUi.Css.StateOk); _prodWarning.EnableInClassList(SampleAppUi.HiddenClass, hasOverride || keyEmpty || isTest); var consent = _initialised ? ImmutableAudience.CurrentConsent : ConsentOrder[Mathf.Clamp(_initialConsent?.index ?? 0, 0, ConsentOrder.Length - 1)]; int cIdx = Array.IndexOf(ConsentOrder, consent); - SetStatusCell(_statusConsent, consent.ToLowercaseString(), cIdx >= 0 ? ConsentStateClass[cIdx] : "dim"); + SetStatusCell(_statusConsent, consent.ToLowercaseString(), cIdx >= 0 ? ConsentStateClass[cIdx] : SampleAppUi.Css.Dim); - SetStatusCell(_statusAnon, _initialised ? ImmutableAudience.AnonymousId : null, "dim"); - SetStatusCell(_statusUser, _initialised ? ImmutableAudience.UserId : null, "dim"); - SetStatusCell(_statusSession, _initialised ? ImmutableAudience.SessionId : null, "dim"); - SetStatusCell(_statusQueue, _initialised ? ImmutableAudience.QueueSize.ToString(CultureInfo.InvariantCulture) : null, "dim"); + SetStatusCell(_statusAnon, _initialised ? ImmutableAudience.AnonymousId : null, SampleAppUi.Css.Dim); + SetStatusCell(_statusUser, _initialised ? ImmutableAudience.UserId : null, SampleAppUi.Css.Dim); + SetStatusCell(_statusSession, _initialised ? ImmutableAudience.SessionId : null, SampleAppUi.Css.Dim); + SetStatusCell(_statusQueue, _initialised ? ImmutableAudience.QueueSize.ToString(CultureInfo.InvariantCulture) : null, SampleAppUi.Css.Dim); } private void RefreshConsentPills() diff --git a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs index 6a16d7626..26f7c3807 100644 --- a/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs +++ b/examples/audience/Assets/SampleApp/Scripts/AudienceSample.cs @@ -71,7 +71,7 @@ private void OnShutdown() => RunAndLog(SampleAppUi.LogLabels.Shutdown, () => _initialised = false; ResetIdentityMirror(); OnSdkStateChanged(); - return "SDK stopped"; + return SampleAppUi.Messages.SdkStopped; }); private void OnReset() => RunAndLog(SampleAppUi.LogLabels.Reset, () => @@ -79,22 +79,22 @@ private void OnReset() => RunAndLog(SampleAppUi.LogLabels.Reset, () => ImmutableAudience.Reset(); ResetIdentityMirror(); OnSdkStateChanged(); - return "anonymous ID regenerated, queue cleared"; + return SampleAppUi.Messages.AnonymousIdRegeneratedQueueCleared; }); private async Task OnFlushAsync() { - try { await ImmutableAudience.FlushAsync(); AppendLog(SampleAppUi.LogLabels.Flush, "queue flushed", LogLevel.Ok, LogSource.App); OnSdkStateChanged(); } + try { await ImmutableAudience.FlushAsync(); AppendLog(SampleAppUi.LogLabels.Flush, SampleAppUi.Messages.QueueFlushed, LogLevel.Ok, LogSource.App); OnSdkStateChanged(); } catch (Exception ex) { AppendLog(SampleAppUi.LogLabels.Flush, ex.Message, LogLevel.Err, LogSource.App); } } private async Task OnDeleteDataAsync() { - AppendLog(SampleAppUi.LogLabels.DeleteData, "erasure request dispatched", LogLevel.Info, LogSource.App); + AppendLog(SampleAppUi.LogLabels.DeleteData, SampleAppUi.Messages.ErasureRequestDispatched, LogLevel.Info, LogSource.App); try { await ImmutableAudience.DeleteData(); - AppendLog(SampleAppUi.LogLabels.DeleteData, "backend acknowledged", LogLevel.Ok, LogSource.App); + AppendLog(SampleAppUi.LogLabels.DeleteData, SampleAppUi.Messages.BackendAcknowledged, LogLevel.Ok, LogSource.App); } catch (Exception ex) { @@ -130,8 +130,8 @@ private void OnSendCatalogueEvent(EventSpec spec, Dictionary { - ["event"] = spec.Name, - ["overload"] = "typed", + [SampleAppUi.LogPayloadKeys.Event] = spec.Name, + [SampleAppUi.LogPayloadKeys.Overload] = SampleAppUi.LogPayloadKeys.OverloadValues.Typed, [MessageFields.Properties] = typed.ToProperties(), }, 2); } @@ -139,8 +139,8 @@ private void OnSendCatalogueEvent(EventSpec spec, Dictionary 0 ? props : null); return Json.Serialize(new Dictionary { - ["event"] = spec.Name, - ["overload"] = "string", + [SampleAppUi.LogPayloadKeys.Event] = spec.Name, + [SampleAppUi.LogPayloadKeys.Overload] = SampleAppUi.LogPayloadKeys.OverloadValues.String, [MessageFields.Properties] = props, }, 2); }); @@ -154,7 +154,7 @@ private void OnSendCustomEvent() => RunAndLog(SampleAppUi.LogLabels.Track, () => var f = CaptureCustomEventForm(); var props = string.IsNullOrEmpty(f.RawProps) ? null : JsonReader.DeserializeObject(f.RawProps); ImmutableAudience.Track(f.Name, props); - var echo = new Dictionary { ["event"] = f.Name }; + var echo = new Dictionary { [SampleAppUi.LogPayloadKeys.Event] = f.Name }; if (props != null) echo[MessageFields.Properties] = props; return Json.Serialize(echo, 2); }); @@ -171,14 +171,14 @@ private void OnSetConsent(ConsentLevel level) => RunAndLog(SampleAppUi.LogLabels if (!level.CanIdentify()) ResetIdentityMirror(); var payload = new Dictionary { - ["from"] = previous.ToLowercaseString(), - ["to"] = level.ToLowercaseString(), + [SampleAppUi.LogPayloadKeys.From] = previous.ToLowercaseString(), + [SampleAppUi.LogPayloadKeys.To] = level.ToLowercaseString(), }; var effects = new List(); if (previous == ConsentLevel.None && level != ConsentLevel.None) effects.Add(SampleAppUi.Messages.QueueStartedSessionCreated); if (level == ConsentLevel.None) effects.Add(SampleAppUi.Messages.QueuePurgedAnonymousIdCleared); if (!level.CanIdentify() && previous.CanIdentify()) effects.Add(SampleAppUi.Messages.UserIdCleared); - if (effects.Count > 0) payload["effects"] = effects; + if (effects.Count > 0) payload[SampleAppUi.LogPayloadKeys.Effects] = effects; OnSdkStateChanged(); return Json.Serialize(payload, 2); }); @@ -198,9 +198,9 @@ private void OnIdentify() => RunAndLog(SampleAppUi.LogLabels.Identify, () => OnSdkStateChanged(); var payload = new Dictionary { - ["id"] = f.Id, - [MessageFields.IdentityType] = f.Type, - ["accepted"] = accepted, + [SampleAppUi.LogPayloadKeys.Id] = f.Id, + [MessageFields.IdentityType] = f.Type, + [SampleAppUi.LogPayloadKeys.Accepted] = accepted, }; if (traits != null) payload[MessageFields.Traits] = traits; return Json.Serialize(payload, 2); @@ -233,9 +233,9 @@ private void OnAlias() => RunAndLog(SampleAppUi.LogLabels.Alias, () => } return Json.Serialize(new Dictionary { - ["from"] = new Dictionary { ["id"] = f.FromId, [MessageFields.IdentityType] = f.FromType }, - ["to"] = new Dictionary { ["id"] = f.ToId, [MessageFields.IdentityType] = f.ToType }, - ["accepted"] = accepted, + [SampleAppUi.LogPayloadKeys.From] = new Dictionary { [SampleAppUi.LogPayloadKeys.Id] = f.FromId, [MessageFields.IdentityType] = f.FromType }, + [SampleAppUi.LogPayloadKeys.To] = new Dictionary { [SampleAppUi.LogPayloadKeys.Id] = f.ToId, [MessageFields.IdentityType] = f.ToType }, + [SampleAppUi.LogPayloadKeys.Accepted] = accepted, }, 2); }); @@ -246,8 +246,8 @@ private void OnAlias() => RunAndLog(SampleAppUi.LogLabels.Alias, () => private void OnSdkError(AudienceError err) => AppendLog(SampleAppUi.LogLabels.OnError, Json.Serialize(new Dictionary { - ["code"] = err.Code.ToString(), - ["message"] = err.Message, + [SampleAppUi.LogPayloadKeys.Code] = err.Code.ToString(), + [SampleAppUi.LogPayloadKeys.Message] = err.Message, }, 2), LogLevel.Err, LogSource.Sdk); // SDK Log.Writer adapter. May fire from any thread; AppendLog handles @@ -288,7 +288,7 @@ private static void GuardConsentForTrack() var consent = ImmutableAudience.CurrentConsent; if (!consent.CanTrack()) throw new InvalidOperationException( - $"track dropped — consent is {consent.ToLowercaseString()}; raise to anonymous or full to queue events"); + string.Format(SampleAppUi.Messages.TrackDroppedConsentFmt, consent.ToLowercaseString())); } // Refresh* are idempotent reads, so calling all four every time is @@ -320,7 +320,7 @@ private AudienceConfig BuildAudienceConfig(InitForm form, Action if (form.FlushIntervalMs is int flushMs && flushMs > 0) { if (flushMs < 1000) - AppendLog(SampleAppUi.LogLabels.Init, $"flushInterval {flushMs}ms below 1s — clamped", LogLevel.Warn, LogSource.App); + AppendLog(SampleAppUi.LogLabels.Init, string.Format(SampleAppUi.Messages.FlushIntervalBelowOneSecondClampedFmt, flushMs), LogLevel.Warn, LogSource.App); config.FlushIntervalSeconds = Math.Max(1, flushMs / 1000); } if (form.FlushSize is int flushSize && flushSize > 0) @@ -334,17 +334,17 @@ private static Dictionary BuildConfigEcho(AudienceConfig config) { var echo = new Dictionary { - ["consent"] = config.Consent.ToString(), - ["debug"] = config.Debug, - ["flushIntervalSeconds"] = config.FlushIntervalSeconds, - ["flushSize"] = config.FlushSize, - ["packageVersion"] = config.PackageVersion, - ["shutdownFlushTimeoutMs"] = config.ShutdownFlushTimeoutMs, + [SampleAppUi.LogPayloadKeys.Consent] = config.Consent.ToString(), + [SampleAppUi.LogPayloadKeys.Debug] = config.Debug, + [SampleAppUi.LogPayloadKeys.FlushIntervalSeconds] = config.FlushIntervalSeconds, + [SampleAppUi.LogPayloadKeys.FlushSize] = config.FlushSize, + [SampleAppUi.LogPayloadKeys.PackageVersion] = config.PackageVersion, + [SampleAppUi.LogPayloadKeys.ShutdownFlushTimeoutMs] = config.ShutdownFlushTimeoutMs, }; if (!string.IsNullOrEmpty(config.PublishableKey)) - echo["publishableKey"] = RedactPublishableKey(config.PublishableKey); + echo[SampleAppUi.LogPayloadKeys.PublishableKey] = RedactPublishableKey(config.PublishableKey); if (!string.IsNullOrEmpty(config.PersistentDataPath)) - echo["persistentDataPath"] = config.PersistentDataPath; + echo[SampleAppUi.LogPayloadKeys.PersistentDataPath] = config.PersistentDataPath; return echo; } diff --git a/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs b/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs index e48ff4d9a..d842cf876 100644 --- a/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs +++ b/examples/audience/Assets/SampleApp/Scripts/SampleAppUi.cs @@ -160,6 +160,10 @@ internal static class ButtonText internal const string Send = "Send"; internal const string Copy = "Copy"; internal const string Copied = "Copied"; + + // Click-to-copy flash on status cells (transient label override). + // Distinct from Copied which is the post-click button label. + internal const string CopiedFlash = "Copied!"; } // ---- Resources paths ---- @@ -182,9 +186,22 @@ internal static class Messages internal const string QueueStartedSessionCreated = "queue started, session created"; internal const string QueuePurgedAnonymousIdCleared = "queue purged, anonymous ID cleared"; internal const string UserIdCleared = "userId cleared"; - internal const string NoActiveIdentity = "no active identity — call Identify first"; + internal const string NoActiveIdentity = "no active identity, call Identify first"; internal const string TraitsRequired = "traits required"; internal const string Ready = "Sample app loaded. Paste a publishable key and click Init."; + + // Status messages emitted by AudienceSample's RunAndLog handlers. + internal const string SdkStopped = "SDK stopped"; + internal const string AnonymousIdRegeneratedQueueCleared = "anonymous ID regenerated, queue cleared"; + internal const string QueueFlushed = "queue flushed"; + internal const string ErasureRequestDispatched = "erasure request dispatched"; + internal const string BackendAcknowledged = "backend acknowledged"; + + // Formatted variants for use with string.Format or interpolation. + internal const string TrackDroppedConsentFmt = + "track dropped, consent is {0}; raise to anonymous or full to queue events"; + internal const string FlushIntervalBelowOneSecondClampedFmt = + "flushInterval {0}ms below 1s, clamped"; } // Mirrors AudienceSample.UI.cs PopulateTypedEventAccordions naming: @@ -301,5 +318,42 @@ internal static class Consent internal const string Anonymous = "anonymous"; internal const string Full = "full"; } + + // Log payload JSON keys used by RunAndLog "Ok" row dictionaries. + + internal static class LogPayloadKeys + { + // Track outcomes + internal const string Event = "event"; + internal const string Overload = "overload"; + internal const string Effects = "effects"; + + // Identify / Alias outcomes + internal const string Id = "id"; + internal const string Accepted = "accepted"; + internal const string From = "from"; + internal const string To = "to"; + + // OnError row payload + internal const string Code = "code"; + internal const string Message = "message"; + + // Init config echo + internal const string Consent = "consent"; + internal const string Debug = "debug"; + internal const string FlushIntervalSeconds = "flushIntervalSeconds"; + internal const string FlushSize = "flushSize"; + internal const string PackageVersion = "packageVersion"; + internal const string ShutdownFlushTimeoutMs = "shutdownFlushTimeoutMs"; + internal const string PublishableKey = "publishableKey"; + internal const string PersistentDataPath = "persistentDataPath"; + + // Track overload values. + internal static class OverloadValues + { + internal const string Typed = "typed"; + internal const string String = "string"; + } + } } } diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs new file mode 100644 index 000000000..a642eb2f9 --- /dev/null +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs @@ -0,0 +1,10 @@ +namespace Immutable.Audience.Samples.SampleApp.Tests +{ + // Values used by SampleAppLiveFireTests when filling the demo input fields. + internal static class SampleAppLiveFireFixtures + { + internal const string MilestoneSmokeName = "il2cpp_smoke"; + internal const string GameId = "il2cpp_game_1"; + internal const string LinkUrl = "https://example.com/il2cpp"; + } +} diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta new file mode 100644 index 000000000..fab7470eb --- /dev/null +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireFixtures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e230edd97bb4848af6b5988e961d01c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs index 2682fb149..1565cca1d 100644 --- a/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs +++ b/examples/audience/Assets/SampleApp/Tests/Runtime/SampleAppLiveFireTests.cs @@ -172,7 +172,7 @@ public IEnumerator TypedEvent_MilestoneReached_FlushReportsOk() { yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.MilestoneReached), root => { - root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = "il2cpp_smoke"; + root.Q(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = SampleAppLiveFireFixtures.MilestoneSmokeName; }); } @@ -262,7 +262,7 @@ public IEnumerator CustomTrack_WithDictionaryProps_FlushReportsOk() // Custom event name + JSON props (the sample app parses props as JSON // and forwards them as Dictionary to ImmutableAudience.Track). - _root!.Q(SampleAppUi.CustomEvent.Name).value = "il2cpp_smoke"; + _root!.Q(SampleAppUi.CustomEvent.Name).value = SampleAppLiveFireFixtures.MilestoneSmokeName; _root.Q(SampleAppUi.CustomEvent.Props).value = "{\"int_field\":42,\"str_field\":\"hello\",\"bool_field\":true,\"nested\":{\"a\":1}}"; _root.Q