+ {#if isSessionStart}
+
+
+
+ Occurred On
+
+
+
+
+
+
+ Duration
+
+
+ {#if isActiveSession}
+
+ {/if}
+
+ {#if event.data?.sessionend}
+ (ended )
+ {/if}
+
+
+
+
+ {/if}
+
+
Session Events
+
+ {#if sessionEventsQuery.isLoading}
+
+ {#each Array.from({ length: 5 }, (_, i) => i) as i (i)}
+
+ {/each}
+
+ {:else if sessionEventsQuery.isError}
+
+ Error loading session events
+ {sessionEventsQuery.error?.message ?? 'Unknown error'}
+
+ {:else if sessionEventsQuery.data && sessionEventsQuery.data.length > 0}
+
+
+
+ Summary
+ When
+
+
+
+ {#each sessionEventsQuery.data as sessionEvent (sessionEvent.id)}
+
+
+
+ {sessionEvent.id}
+
+
+
+
+
+
+ {/each}
+
+
+ {:else}
+
No session events found.
+ {/if}
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts
index b3be2e34c2..4edfd67dee 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts
@@ -359,6 +359,11 @@ export function postOrganization() {
onSuccess: (organization: ViewOrganization) => {
queryClient.setQueryData(queryKeys.id(organization.id, 'stats'), organization);
queryClient.setQueryData(queryKeys.id(organization.id, undefined), organization);
+ // Invalidate organizations list so it includes the new org
+ queryClient.invalidateQueries({ queryKey: queryKeys.list(undefined) });
+ queryClient.invalidateQueries({ queryKey: queryKeys.list('stats') });
+ // Invalidate user query since organization_ids changed on the backend
+ queryClient.invalidateQueries({ queryKey: ['User', 'me'] });
}
}));
}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/api.svelte.ts
new file mode 100644
index 0000000000..01391d7261
--- /dev/null
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/api.svelte.ts
@@ -0,0 +1,121 @@
+import type { CountResult } from '$shared/models';
+
+import { accessToken } from '$features/auth/index.svelte';
+import { DEFAULT_OFFSET } from '$shared/api/api.svelte';
+import { type ProblemDetails, useFetchClient } from '@exceptionless/fetchclient';
+import { createQuery, useQueryClient } from '@tanstack/svelte-query';
+
+import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index';
+
+export const queryKeys = {
+ organizations: (id: string | undefined) => [...queryKeys.type, 'organizations', id] as const,
+ organizationsCount: (id: string | undefined, params?: GetOrganizationSessionsCountRequest['params']) =>
+ [...queryKeys.organizations(id), 'count', params] as const,
+ projects: (id: string | undefined) => [...queryKeys.type, 'projects', id] as const,
+ projectsCount: (id: string | undefined, params?: GetProjectSessionsCountRequest['params']) => [...queryKeys.projects(id), 'count', params] as const,
+ sessionEvents: (id: string | undefined) => [...queryKeys.type, 'session', id] as const,
+ type: ['Session'] as const
+};
+
+export interface GetSessionsParams {
+ after?: string;
+ before?: string;
+ filter?: string;
+ limit?: number;
+ mode?: 'summary';
+ offset?: string;
+ page?: number;
+ sort?: string;
+ time?: string;
+}
+
+export interface GetOrganizationSessionsCountRequest {
+ enabled?: () => boolean;
+ params?: {
+ aggregations?: string;
+ filter?: string;
+ offset?: string;
+ time?: string;
+ };
+ route: {
+ organizationId: string | undefined;
+ };
+}
+
+export interface GetProjectSessionsCountRequest {
+ params?: {
+ aggregations?: string;
+ filter?: string;
+ offset?: string;
+ time?: string;
+ };
+ route: {
+ projectId: string | undefined;
+ };
+}
+
+export interface GetSessionEventsRequest {
+ params?: {
+ after?: string;
+ before?: string;
+ filter?: string;
+ limit?: number;
+ mode?: 'summary';
+ offset?: string;
+ sort?: string;
+ time?: string;
+ };
+ route: {
+ sessionId: string | undefined;
+ };
+}
+
+/**
+ * Get session count with aggregations for stats and chart data.
+ * Uses aggregation: avg:value cardinality:user date:(date^offset cardinality:user)
+ */
+export function getOrganizationSessionsCountQuery(request: GetOrganizationSessionsCountRequest) {
+ const queryClient = useQueryClient();
+
+ return createQuery