diff --git a/src/Exceptionless.Core/Utility/RandomEventGenerator.cs b/src/Exceptionless.Core/Utility/RandomEventGenerator.cs index b9097e0c64..d552a360bc 100644 --- a/src/Exceptionless.Core/Utility/RandomEventGenerator.cs +++ b/src/Exceptionless.Core/Utility/RandomEventGenerator.cs @@ -134,13 +134,7 @@ private void PopulateEvent(Event ev) MachineName = MachineNames.Random() }; - for (int i = 0; i < RandomData.GetInt(1, 3); i++) - { - string key = RandomData.GetWord(); - while (ev.Data.ContainsKey(key) || key == Event.KnownDataKeys.Error) - key = RandomData.GetWord(); - ev.Data.Add(key, RandomData.GetString()); - } + AddSampleExtendedData(ev, identity); int tagCount = RandomData.GetInt(1, 3); for (int i = 0; i < tagCount; i++) @@ -177,14 +171,7 @@ private Error GenerateError(int maxNesting = 3, int currentLevel = 0) if (RandomData.GetBool()) error.Code = RandomData.GetInt(-234523453, 98690899).ToString(); - error.Data = new DataDictionary(); - for (int i = 0; i < RandomData.GetInt(1, 3); i++) - { - string key = RandomData.GetWord(); - while (error.Data.ContainsKey(key) || key == Event.KnownDataKeys.Error) - key = RandomData.GetWord(); - error.Data.Add(key, RandomData.GetString()); - } + error.Data = GenerateErrorData(); var stack = new StackFrameCollection(); for (int i = 0; i < RandomData.GetInt(2, 8); i++) @@ -205,14 +192,7 @@ private SimpleError GenerateSimpleError(int maxNesting = 3, int currentLevel = 0 Type = ExceptionTypes.Random() }; - error.Data = new DataDictionary(); - for (int i = 0; i < RandomData.GetInt(1, 3); i++) - { - string key = RandomData.GetWord(); - while (error.Data.ContainsKey(key) || key == Event.KnownDataKeys.Error) - key = RandomData.GetWord(); - error.Data.Add(key, RandomData.GetString()); - } + error.Data = GenerateErrorData(); error.StackTrace = RandomData.GetString(); @@ -233,6 +213,94 @@ private static StackFrame GenerateStackFrame() }; } + private static void AddSampleExtendedData(Event ev, string? identity) + { + var data = ev.Data ??= new DataDictionary(); + + data["Customer"] = new DataDictionary + { + ["id"] = $"cus_{RandomData.GetInt(100000, 999999)}", + ["email"] = identity ?? Identities.Random(), + ["plan"] = Plans.Random(), + ["region"] = Regions.Random(), + ["account_age_days"] = RandomData.GetInt(7, 1600) + }; + + data["Deployment"] = new DataDictionary + { + ["environment"] = Environments.Random(), + ["version"] = RandomData.GetVersion("2.0", "4.0"), + ["commit"] = ObjectId.GenerateNewId().ToString()[..7], + ["region"] = Regions.Random(), + ["instance"] = MachineNames.Random() + }; + + switch (ev.Type) + { + case Event.KnownTypes.FeatureUsage: + data["Feature Flags"] = new DataDictionary + { + ["dashboard_refresh"] = RandomData.GetBool(), + ["billing_portal"] = RandomData.GetBool(), + ["new_events_view"] = RandomData.GetBool(), + ["checkout_variant"] = FeatureVariants.Random() + }; + break; + case Event.KnownTypes.Log: + data["Background Job"] = new DataDictionary + { + ["id"] = $"job_{RandomData.GetInt(100000, 999999)}", + ["name"] = JobNames.Random(), + ["queue"] = Queues.Random(), + ["attempt"] = RandomData.GetInt(1, 4), + ["duration_ms"] = RandomData.GetInt(25, 15000) + }; + break; + case Event.KnownTypes.NotFound: + data["Route Match"] = new DataDictionary + { + ["requested_path"] = ev.Source, + ["referer"] = PageNames.Random(), + ["rule"] = "legacy-route-redirect", + ["locale"] = Locales.Random(), + ["bot"] = RandomData.GetBool(20) + }; + break; + default: + data["Checkout"] = new DataDictionary + { + ["order_id"] = $"ord_{RandomData.GetInt(100000, 999999)}", + ["cart_id"] = $"cart_{RandomData.GetInt(100000, 999999)}", + ["total"] = RandomData.GetInt(2499, 25999) / 100m, + ["currency"] = "USD", + ["item_count"] = RandomData.GetInt(1, 8), + ["payment_provider"] = PaymentProviders.Random() + }; + break; + } + } + + private static DataDictionary GenerateErrorData() + { + return new DataDictionary + { + ["correlation_id"] = ObjectId.GenerateNewId().ToString(), + ["tenant"] = new DataDictionary + { + ["id"] = $"org_{RandomData.GetInt(10000, 99999)}", + ["plan"] = Plans.Random(), + ["region"] = Regions.Random() + }, + ["operation"] = new DataDictionary + { + ["name"] = Operations.Random(), + ["attempt"] = RandomData.GetInt(1, 4), + ["elapsed_ms"] = RandomData.GetInt(20, 30000), + ["retryable"] = RandomData.GetBool() + } + }; + } + private static readonly List Identities = [ "eric@exceptionless.io", @@ -242,6 +310,51 @@ private static StackFrame GenerateStackFrame() "user42@example.com" ]; + private static readonly List Plans = + [ + "Free", "Team", "Business", "Enterprise" + ]; + + private static readonly List Regions = + [ + "us-east-1", "us-west-2", "eu-west-1", "ap-southeast-2" + ]; + + private static readonly List Environments = + [ + "Production", "Staging", "Preview" + ]; + + private static readonly List FeatureVariants = + [ + "control", "variant-a", "variant-b", "staff-only" + ]; + + private static readonly List PaymentProviders = + [ + "Stripe", "Braintree", "Adyen", "PayPal" + ]; + + private static readonly List JobNames = + [ + "ProcessEventBatch", "SendNotificationEmail", "SyncBillingUsage", "RebuildStackSummary" + ]; + + private static readonly List Queues = + [ + "events", "notifications", "billing", "maintenance" + ]; + + private static readonly List Locales = + [ + "en-US", "en-GB", "fr-FR", "de-DE" + ]; + + private static readonly List Operations = + [ + "LoadProjectSettings", "SubmitCheckout", "SearchEvents", "ProcessQueueMessage" + ]; + private static readonly List MachineIpAddresses = [ "127.34.36.89", "45.66.89.98", "10.12.18.193", "16.89.17.197", "43.10.99.234" diff --git a/src/Exceptionless.Web/ClientApp/src/app.css b/src/Exceptionless.Web/ClientApp/src/app.css index ed44232e36..2194bb62d5 100644 --- a/src/Exceptionless.Web/ClientApp/src/app.css +++ b/src/Exceptionless.Web/ClientApp/src/app.css @@ -7,7 +7,7 @@ --background: hsl(0 0% 100%); --foreground: hsl(221 39% 11%); - --muted: hsl(210 20% 98%); + --muted: hsl(240 4.8% 95.9%); --muted-foreground: hsl(240 3.8% 46.1%); --popover: hsl(0 0% 100%); @@ -22,7 +22,7 @@ --primary: hsl(96 64% 46%); --primary-foreground: hsl(0 0% 10%); - --secondary: hsl(210 20% 98%); + --secondary: hsl(240 4.8% 95.9%); --secondary-foreground: hsl(240 5.9% 10%); --accent: hsl(220 14.29% 95.88%); @@ -151,6 +151,14 @@ @apply border-border; } + html { + overflow-y: scroll; + } + + html:has([data-slot='sheet-content']) { + overflow-y: hidden; + } + body { @apply bg-background text-foreground; } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/event-detail-sheet.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/event-detail-sheet.svelte index 1e9d87205e..8e80de8012 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/event-detail-sheet.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/event-detail-sheet.svelte @@ -32,7 +32,7 @@ - + Event Details + {/if} + + + {#each tabs as tab (tab)} + + {tab} + + {/each} + + + {#if areTabsScrollable} + + {/if} + {#each tabs as tab (tab)} @@ -185,8 +301,8 @@ {:else if tab === 'Trace Log'} - {:else if tab === 'Session Events'} - + {:else if tab === 'Session'} + {:else if tab === 'Extended Data'} {:else} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte index ebbd97c2d0..b59cb11a94 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte @@ -1,14 +1,21 @@ {#if !hasPremiumFeatures} @@ -83,7 +137,20 @@ {/if} -

Session Events

+
+

Session Events

+ +
{#if sessionEventsQuery.isPending}
@@ -106,7 +173,15 @@ {#each sessionEventsQuery.data ?? [] as sessionEvent (sessionEvent.id)} - + openSessionEvent(sessionEvent.id)} + onkeydown={(keyboardEvent) => handleSessionEventKeydown(keyboardEvent, sessionEvent.id)} + role="link" + tabindex={0} + title="Open event details" + > diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts index 5748b9f3c2..63bf2914da 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts @@ -449,9 +449,13 @@ export function postPromotedTab(request: PostPromotedTabRequest) { return createMutation(() => ({ mutationFn: async (params: PostPromotedTabParams) => { const client = useFetchClient(); - const response = await client.post(`projects/${request.route.id}/promotedtabs`, undefined, { - params: { ...params } - }); + const response = await client.postJSON( + `projects/${request.route.id}/promotedtabs`, + {}, + { + params: { ...params } + } + ); return response.ok; }, diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/navigation/nav-container.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/navigation/nav-container.svelte index d295f2bb6d..23d5aa1dac 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/navigation/nav-container.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/navigation/nav-container.svelte @@ -4,12 +4,13 @@ interface Props extends HTMLAttributes { children: Snippet; + orientation?: 'horizontal' | 'responsive'; } - let { children, class: className, ...props }: Props = $props(); + let { children, class: className, orientation = 'responsive', ...props }: Props = $props(); // TODO: Look into having this use the sidbar menu items and a tab control on mobile. -