{#if isLoading}
-
+
{:else}
-
+
{/if}
-
-
- Average Duration
-
+
+
-
+
{#if isLoading}
-
+
{:else}
-
- {#if avgDuration > 0}
-
-
- {:else}
- โ
- {/if}
+
+ {compactAverageDuration}
+ {preciseAverageDuration}
{/if}
+
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-shell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-shell.svelte
new file mode 100644
index 000000000..9b7813452
--- /dev/null
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-shell.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
\ No newline at end of file
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/index.ts
index f22375e09..58bd3b114 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/index.ts
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/index.ts
@@ -1,6 +1,7 @@
import ChartContainer from "./chart-container.svelte";
+import ChartShell from "./chart-shell.svelte";
import ChartTooltip from "./chart-tooltip.svelte";
export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js";
-export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip };
+export { ChartContainer, ChartShell, ChartTooltip, ChartContainer as Container, ChartShell as Shell, ChartTooltip as Tooltip };
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sheet/sheet-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sheet/sheet-content.svelte
index 703dd4e3b..9a479f9c2 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sheet/sheet-content.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sheet/sheet-content.svelte
@@ -17,10 +17,12 @@
class: className,
side = "right",
showCloseButton = true,
+ overlayProps,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild & {
+ overlayProps?: WithoutChildrenOrChild>;
portalProps?: WithoutChildrenOrChild>;
side?: Side;
showCloseButton?: boolean;
@@ -29,7 +31,7 @@
-
+
;
- column_order?: null | string[];
+ column_order?: string[] | null;
+ show_stats?: null | boolean;
+ show_chart?: null | boolean;
/** If true, the view will only be visible to the current user. Defaults to false. */
is_private?: null | boolean;
}
@@ -219,7 +221,10 @@ export interface PersistentEvent {
created_utc: string;
/** Used to store primitive data type custom data values for searching the event. */
idx?: null | Record;
- /** The event type (ie. error, log message, feature usage). Check KnownTypes for standard event types. */
+ /**
+ * The event type (ie. error, log message, feature usage). Check KnownTypes for standard event types.
+ * Nullable in transit; the pipeline infers a default before save. Validated as required on repository save.
+ */
type?: null | string;
/** The event source (ie. machine name, log name, feature name). */
source?: null | string;
@@ -372,7 +377,9 @@ export interface UpdateSavedView {
sort?: null | string;
filter_definitions?: null | string;
columns?: null | Record;
- column_order?: null | string[];
+ column_order?: string[] | null;
+ show_stats?: null | boolean;
+ show_chart?: null | boolean;
}
/** A class the tracks changes (i.e. the Delta) for a particular TEntityType. */
@@ -564,7 +571,9 @@ export interface ViewSavedView {
filter?: null | string;
filter_definitions?: null | string;
columns?: null | Record;
- column_order?: null | string[];
+ column_order?: string[] | null;
+ show_stats?: null | boolean;
+ show_chart?: null | boolean;
name: string;
time?: null | string;
sort?: null | string;
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts b/src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts
index 8667139eb..b4f664af0 100644
--- a/src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts
+++ b/src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts
@@ -202,6 +202,8 @@ export const NewSavedViewSchema = object({
.optional(),
columns: record(string(), boolean()).nullable().optional(),
column_order: array(string()).nullable().optional(),
+ show_stats: boolean().nullable().optional(),
+ show_chart: boolean().nullable().optional(),
is_private: boolean().nullable().optional(),
});
export type NewSavedViewFormData = Infer;
@@ -288,8 +290,7 @@ export const PersistentEventSchema = object({
type: string()
.min(1, "Type is required")
.max(100, "Type must be at most 100 characters")
- .nullable()
- .optional(),
+ .nullable(),
source: string()
.min(1, "Source is required")
.max(2000, "Source must be at most 2000 characters")
@@ -424,6 +425,8 @@ export const UpdateSavedViewSchema = object({
.optional(),
columns: record(string(), boolean()).nullable().optional(),
column_order: array(string()).nullable().optional(),
+ show_stats: boolean().nullable().optional(),
+ show_chart: boolean().nullable().optional(),
});
export type UpdateSavedViewFormData = Infer;
@@ -613,6 +616,8 @@ export const ViewSavedViewSchema = object({
.optional(),
columns: record(string(), boolean()).nullable().optional(),
column_order: array(string()).nullable().optional(),
+ show_stats: boolean().nullable().optional(),
+ show_chart: boolean().nullable().optional(),
name: string().min(1, "Name is required"),
time: string().min(1, "Time is required").nullable().optional(),
sort: string().min(1, "Sort is required").nullable().optional(),
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte
index c35d0c38d..2774abbe5 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte
@@ -27,7 +27,7 @@
{#if isMediumScreenQuery.current}
-
+
{:else}
{/if}
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte
index 633f77c66..6979800f9 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte
@@ -1,9 +1,21 @@
{#key resetKey}
-
-
+
+
No results found.
+ {#if hasSearchText && showRemoteSearchResults}
+ {#if showEventSearchResults}
+
+ {#if eventSearchQuery.isPending}
+
+
+ Searching events...
+
+ {:else}
+ {#each eventMatches as event (event.id)}
+
+
+
+ {getResultTitle(event)}
+ {#if getResultDescription(event)}
+ {getResultDescription(event)}
+ {/if}
+
+
+ {/each}
+ {#if hasMoreEventMatches}
+
+
+ View all matching events
+
+ {/if}
+ {/if}
+
+ {/if}
+ {#if showEventSearchResults && showIssueSearchResults}
+
+ {/if}
+ {#if showIssueSearchResults}
+
+ {#if issueSearchQuery.isPending}
+
+
+ Searching issues...
+
+ {:else}
+ {#each issueMatches as issue (issue.id)}
+
+
+
+ {getResultTitle(issue)}
+ {#if getResultDescription(issue)}
+ {getResultDescription(issue)}
+ {/if}
+
+
+ {/each}
+ {#if hasMoreIssueMatches}
+
+
+ View all matching issues
+
+ {/if}
+ {/if}
+
+ {/if}
+
+ {/if}
{#each Object.entries(groupedRoutes) as [group, items], index (group)}
{#each items as route (route.href)}
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
index 26769721f..f869cfe77 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte
@@ -12,6 +12,7 @@
import { getOrganizationCountQuery } from '$features/events/api.svelte';
import EventDetailSheet from '$features/events/components/event-detail-sheet.svelte';
import EventsDashboardChart from '$features/events/components/events-dashboard-chart.svelte';
+ import EventsStatsDashboard from '$features/events/components/events-stats-dashboard.svelte';
import { DateFilter, ProjectFilter, StatusFilter } from '$features/events/components/filters';
import {
applyTimeFilter,
@@ -92,15 +93,21 @@
});
const VIEW = 'events';
+ let showStats = $state(true);
+ let showChart = $state(true);
const savedViewsState = useSavedViews({
defaultColumnVisibility: defaultEventColumnVisibility,
filterCacheKey,
getColumnOrder: () => table.store.state.columnOrder,
getColumnVisibility: () => table.store.state.columnVisibility,
getFilterDefinitions: () => serializeFilters(filters ?? []),
+ getShowChart: () => showChart,
+ getShowStats: () => showStats,
queryParams,
setColumnOrder: (v) => table.setColumnOrder(v),
setColumnVisibility: (v) => table.setColumnVisibility(v),
+ setShowChart: (v) => (showChart = v),
+ setShowStats: (v) => (showStats = v),
updateFilterCache,
view: VIEW
});
@@ -297,6 +304,22 @@
}));
});
+ const stats = $derived.by(() => {
+ const aggregations = chartDataQuery.data?.aggregations;
+ const timeRange = parseDateMathRange(getQueryTime() || undefined);
+ const totalEvents = agg.sum(aggregations, 'sum_count')?.value ?? chartDataQuery.data?.total ?? 0;
+ const totalIssues = agg.cardinality(aggregations, 'cardinality_stack')?.value ?? 0;
+ const newIssues = agg.terms(aggregations, 'terms_first')?.buckets[0]?.total ?? 0;
+ const hours = Math.max((timeRange.end.getTime() - timeRange.start.getTime()) / 3_600_000, 1);
+
+ return {
+ eventsPerHour: totalEvents / hours,
+ newIssues,
+ totalEvents,
+ totalIssues
+ };
+ });
+
function onRangeSelect(start: Date, end: Date) {
onFilterChanged(new DateFilter('date', toDateMathRange(start, end)));
}
@@ -322,6 +345,10 @@
onClearSavedView={savedViewsState.handleClearSavedView}
onResetToSaved={savedViewsState.handleResetToSaved}
savedViews={savedViewsState.savedViews}
+ {showChart}
+ {showStats}
+ setShowChart={(v) => (showChart = v)}
+ setShowStats={(v) => (showStats = v)}
sort={queryParams.sort ?? undefined}
{table}
time={queryParams.time ?? undefined}
@@ -338,7 +365,19 @@
-
+ {#if showStats}
+
+ {/if}
+
+ {#if showChart}
+
+ {/if}
{#snippet footerChildren()}
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
index 160e52ba4..d1c17b877 100644
--- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
+++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte
@@ -11,6 +11,7 @@
import { type GetEventsParams, getOrganizationCountQuery, getStackEventsQuery } from '$features/events/api.svelte';
import EventDetailSheet from '$features/events/components/event-detail-sheet.svelte';
import EventsDashboardChart from '$features/events/components/events-dashboard-chart.svelte';
+ import EventsStatsDashboard from '$features/events/components/events-stats-dashboard.svelte';
import { DateFilter, ProjectFilter, StatusFilter, TypeFilter } from '$features/events/components/filters';
import {
applyTimeFilter,
@@ -106,14 +107,20 @@
});
const VIEW = 'issues';
+ let showStats = $state(true);
+ let showChart = $state(true);
const savedViewsState = useSavedViews({
filterCacheKey,
getColumnOrder: () => table.store.state.columnOrder,
getColumnVisibility: () => table.store.state.columnVisibility,
getFilterDefinitions: () => serializeFilters(filters ?? []),
+ getShowChart: () => showChart,
+ getShowStats: () => showStats,
queryParams,
setColumnOrder: (v) => table.setColumnOrder(v),
setColumnVisibility: (v) => table.setColumnVisibility(v),
+ setShowChart: (v) => (showChart = v),
+ setShowStats: (v) => (showStats = v),
updateFilterCache,
view: VIEW
});
@@ -310,6 +317,22 @@
}));
});
+ const stats = $derived.by(() => {
+ const aggregations = chartDataQuery.data?.aggregations;
+ const timeRange = parseDateMathRange(queryParams.time);
+ const totalEvents = agg.sum(aggregations, 'sum_count')?.value ?? chartDataQuery.data?.total ?? 0;
+ const totalIssues = agg.cardinality(aggregations, 'cardinality_stack')?.value ?? 0;
+ const newIssues = agg.terms(aggregations, 'terms_first')?.buckets[0]?.total ?? 0;
+ const hours = Math.max((timeRange.end.getTime() - timeRange.start.getTime()) / 3_600_000, 1);
+
+ return {
+ eventsPerHour: totalEvents / hours,
+ newIssues,
+ totalEvents,
+ totalIssues
+ };
+ });
+
function onRangeSelect(start: Date, end: Date) {
onFilterChanged(new DateFilter('date', toDateMathRange(start, end)));
}
@@ -335,6 +358,10 @@
onClearSavedView={savedViewsState.handleClearSavedView}
onResetToSaved={savedViewsState.handleResetToSaved}
savedViews={savedViewsState.savedViews}
+ {showChart}
+ {showStats}
+ setShowChart={(v) => (showChart = v)}
+ setShowStats={(v) => (showStats = v)}
{table}
time={queryParams.time ?? undefined}
view={VIEW}
@@ -350,7 +377,19 @@
-
+ {#if showStats}
+
+ {/if}
+
+ {#if showChart}
+
+ {/if}
{#snippet footerChildren()}
diff --git a/src/Exceptionless.Web/Models/SavedView/NewSavedView.cs b/src/Exceptionless.Web/Models/SavedView/NewSavedView.cs
index 0010c0c81..f8e324768 100644
--- a/src/Exceptionless.Web/Models/SavedView/NewSavedView.cs
+++ b/src/Exceptionless.Web/Models/SavedView/NewSavedView.cs
@@ -54,6 +54,10 @@ public record NewSavedView : IOwnedByOrganization, IValidatableObject
[MaxLength(50)]
public List? ColumnOrder { get; set; }
+ public bool? ShowStats { get; set; }
+
+ public bool? ShowChart { get; set; }
+
/// If true, the view will only be visible to the current user. Defaults to false.
public bool? IsPrivate { get; set; }
diff --git a/src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs b/src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs
index e39852df9..d094e8595 100644
--- a/src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs
+++ b/src/Exceptionless.Web/Models/SavedView/UpdateSavedView.cs
@@ -19,6 +19,8 @@ public class UpdateSavedView : IValidatableObject
public Dictionary? Columns { get; set; }
[MaxLength(50)]
public List? ColumnOrder { get; set; }
+ public bool? ShowStats { get; set; }
+ public bool? ShowChart { get; set; }
public IEnumerable Validate(ValidationContext validationContext)
{
diff --git a/src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs b/src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs
index 7022e650b..326ff94c8 100644
--- a/src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs
+++ b/src/Exceptionless.Web/Models/SavedView/ViewSavedView.cs
@@ -24,6 +24,8 @@ public record ViewSavedView : IIdentity, IHaveDates
public string? FilterDefinitions { get; set; }
public Dictionary? Columns { get; set; }
public List? ColumnOrder { get; set; }
+ public bool? ShowStats { get; set; }
+ public bool? ShowChart { get; set; }
public string Name { get; set; } = null!;
public string? Time { get; set; }
public string? Sort { get; set; }
diff --git a/src/Exceptionless.Web/Utility/OpenApi/DeltaSchemaTransformer.cs b/src/Exceptionless.Web/Utility/OpenApi/DeltaSchemaTransformer.cs
index dc2c714b4..34f000b90 100644
--- a/src/Exceptionless.Web/Utility/OpenApi/DeltaSchemaTransformer.cs
+++ b/src/Exceptionless.Web/Utility/OpenApi/DeltaSchemaTransformer.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Exceptionless.Core.Extensions;
using Microsoft.AspNetCore.OpenApi;
@@ -40,6 +41,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
// Apply data annotations from the inner type's property
DataAnnotationHelper.ApplyToSchema(propertySchema, property);
+ ApplyArrayAnnotations(propertySchema, property);
string propertyName = property.Name.ToLowerUnderscoredWords();
schema.Properties[propertyName] = propertySchema;
@@ -133,9 +135,10 @@ private static OpenApiSchema CreateSchemaForType(Type type, bool isNullable)
schema.AdditionalProperties = CreateSchemaForType(valueType, false);
}
}
- else if (type.IsArray || (type.IsGenericType && typeof(System.Collections.IEnumerable).IsAssignableFrom(type)))
+ else if (TryGetEnumerableElementType(type, out var elementType))
{
schemaType |= JsonSchemaType.Array;
+ schema.Items = CreateSchemaForType(elementType, false);
}
else
{
@@ -145,4 +148,40 @@ private static OpenApiSchema CreateSchemaForType(Type type, bool isNullable)
schema.Type = schemaType;
return schema;
}
+
+ private static void ApplyArrayAnnotations(OpenApiSchema schema, PropertyInfo property)
+ {
+ if (!schema.Type.HasValue || (schema.Type.Value & JsonSchemaType.Array) != JsonSchemaType.Array)
+ {
+ return;
+ }
+
+ var maxLength = property.GetCustomAttribute();
+ if (maxLength is { Length: > -1 })
+ {
+ schema.MaxItems = maxLength.Length;
+ }
+ }
+
+ private static bool TryGetEnumerableElementType(Type type, out Type elementType)
+ {
+ if (type.IsArray)
+ {
+ elementType = type.GetElementType() ?? typeof(object);
+ return true;
+ }
+
+ if (type == typeof(string) || !typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
+ {
+ elementType = typeof(object);
+ return false;
+ }
+
+ var enumerableType = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)
+ ? type
+ : type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
+
+ elementType = enumerableType?.GetGenericArguments()[0] ?? typeof(object);
+ return true;
+ }
}
diff --git a/tests/Exceptionless.Tests/Controllers/Data/openapi.json b/tests/Exceptionless.Tests/Controllers/Data/openapi.json
index db7dddf49..ace5b2d4c 100644
--- a/tests/Exceptionless.Tests/Controllers/Data/openapi.json
+++ b/tests/Exceptionless.Tests/Controllers/Data/openapi.json
@@ -440,7 +440,7 @@
"Token"
],
"summary": "Create for organization",
- "description": "This is a helper action that makes it easier to create a token for a specific organization.\nYou may also specify a scope when creating a token. There are three valid scopes: client, user and admin.",
+ "description": "This is a helper action that makes it easier to create a token for a specific organization.\r\nYou may also specify a scope when creating a token. There are three valid scopes: client, user and admin.",
"parameters": [
{
"name": "organizationId",
@@ -564,7 +564,7 @@
"Token"
],
"summary": "Create for project",
- "description": "This is a helper action that makes it easier to create a token for a specific project.\nYou may also specify a scope when creating a token. There are three valid scopes: client, user and admin.",
+ "description": "This is a helper action that makes it easier to create a token for a specific project.\r\nYou may also specify a scope when creating a token. There are three valid scopes: client, user and admin.",
"parameters": [
{
"name": "projectId",
@@ -1071,7 +1071,7 @@
"Auth"
],
"summary": "Login",
- "description": "Log in with your email address and password to generate a token scoped with your users roles.\n\n```{ \"email\": \"noreply@exceptionless.io\", \"password\": \"exceptionless\" }```\n\nThis token can then be used to access the api. You can use this token in the header (bearer authentication)\nor append it onto the query string: ?access_token=MY_TOKEN\n\nPlease note that you can also use this token on the documentation site by placing it in the\nheaders api_key input box.",
+ "description": "Log in with your email address and password to generate a token scoped with your users roles.\r\n\r\n```{ \"email\": \"noreply@exceptionless.io\", \"password\": \"exceptionless\" }```\r\n\r\nThis token can then be used to access the api. You can use this token in the header (bearer authentication)\r\nor append it onto the query string: ?access_token=MY_TOKEN\r\n\r\nPlease note that you can also use this token on the documentation site by placing it in the\r\nheaders api_key input box.",
"requestBody": {
"content": {
"application/json": {
@@ -1928,7 +1928,7 @@
"Event"
],
"summary": "Submit event by POST",
- "description": " You can create an event by posting any uncompressed or compressed (gzip or deflate) string or json object. If we know how to handle it\n we will create a new event. If none of the JSON properties match the event object then we will create a new event and place your JSON\n object into the events data collection.\n\n You can also post a multi-line string. We automatically split strings by the \\n character and create a new log event for every line.\n\n Simple event:\n ```{ \"message\": \"Exceptionless is amazing!\" }```\n\n Simple log event with user identity:\n ```{\n \"type\": \"log\",\n \"message\": \"Exceptionless is amazing!\",\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\n \"@user\":{ \"identity\":\"123456789\", \"name\": \"Test User\" }\n}```\n\n Multiple events from string content:\n ```Exceptionless is amazing!\nExceptionless is really amazing!```\n\n Simple error:\n ```{\n \"type\": \"error\",\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\n \"@simple_error\": {\n \"message\": \"Simple Exception\",\n \"type\": \"System.Exception\",\n \"stack_trace\": \" at Client.Tests.ExceptionlessClientTests.CanSubmitSimpleException() in ExceptionlessClientTests.cs:line 77\"\n }\n}```",
+ "description": " You can create an event by posting any uncompressed or compressed (gzip or deflate) string or json object. If we know how to handle it\r\n we will create a new event. If none of the JSON properties match the event object then we will create a new event and place your JSON\r\n object into the events data collection.\r\n\r\n You can also post a multi-line string. We automatically split strings by the \\n character and create a new log event for every line.\r\n\r\n Simple event:\r\n ```{ \"message\": \"Exceptionless is amazing!\" }```\r\n\r\n Simple log event with user identity:\r\n ```{\r\n \"type\": \"log\",\r\n \"message\": \"Exceptionless is amazing!\",\r\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\r\n \"@user\":{ \"identity\":\"123456789\", \"name\": \"Test User\" }\r\n}```\r\n\r\n Multiple events from string content:\r\n ```Exceptionless is amazing!\r\nExceptionless is really amazing!```\r\n\r\n Simple error:\r\n ```{\r\n \"type\": \"error\",\r\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\r\n \"@simple_error\": {\r\n \"message\": \"Simple Exception\",\r\n \"type\": \"System.Exception\",\r\n \"stack_trace\": \" at Client.Tests.ExceptionlessClientTests.CanSubmitSimpleException() in ExceptionlessClientTests.cs:line 77\"\r\n }\r\n}```",
"parameters": [
{
"name": "userAgent",
@@ -2214,7 +2214,7 @@
"Event"
],
"summary": "Submit event by POST for a specific project",
- "description": " You can create an event by posting any uncompressed or compressed (gzip or deflate) string or json object. If we know how to handle it\n we will create a new event. If none of the JSON properties match the event object then we will create a new event and place your JSON\n object into the events data collection.\n\n You can also post a multi-line string. We automatically split strings by the \\n character and create a new log event for every line.\n\n Simple event:\n ```{ \"message\": \"Exceptionless is amazing!\" }```\n\n Simple log event with user identity:\n ```{\n \"type\": \"log\",\n \"message\": \"Exceptionless is amazing!\",\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\n \"@user\":{ \"identity\":\"123456789\", \"name\": \"Test User\" }\n}```\n\n Multiple events from string content:\n ```Exceptionless is amazing!\nExceptionless is really amazing!```\n\n Simple error:\n ```{\n \"type\": \"error\",\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\n \"@simple_error\": {\n \"message\": \"Simple Exception\",\n \"type\": \"System.Exception\",\n \"stack_trace\": \" at Client.Tests.ExceptionlessClientTests.CanSubmitSimpleException() in ExceptionlessClientTests.cs:line 77\"\n }\n}```",
+ "description": " You can create an event by posting any uncompressed or compressed (gzip or deflate) string or json object. If we know how to handle it\r\n we will create a new event. If none of the JSON properties match the event object then we will create a new event and place your JSON\r\n object into the events data collection.\r\n\r\n You can also post a multi-line string. We automatically split strings by the \\n character and create a new log event for every line.\r\n\r\n Simple event:\r\n ```{ \"message\": \"Exceptionless is amazing!\" }```\r\n\r\n Simple log event with user identity:\r\n ```{\r\n \"type\": \"log\",\r\n \"message\": \"Exceptionless is amazing!\",\r\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\r\n \"@user\":{ \"identity\":\"123456789\", \"name\": \"Test User\" }\r\n}```\r\n\r\n Multiple events from string content:\r\n ```Exceptionless is amazing!\r\nExceptionless is really amazing!```\r\n\r\n Simple error:\r\n ```{\r\n \"type\": \"error\",\r\n \"date\":\"2030-01-01T12:00:00.0000000-05:00\",\r\n \"@simple_error\": {\r\n \"message\": \"Simple Exception\",\r\n \"type\": \"System.Exception\",\r\n \"stack_trace\": \" at Client.Tests.ExceptionlessClientTests.CanSubmitSimpleException() in ExceptionlessClientTests.cs:line 77\"\r\n }\r\n}```",
"parameters": [
{
"name": "projectId",
@@ -3547,7 +3547,7 @@
"Event"
],
"summary": "Submit event by GET",
- "description": "You can submit an event using an HTTP GET and query string parameters. Any unknown query string parameters will be added to the extended data of the event.\n\nFeature usage named build with a duration of 10:\n```/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\n\nLog with message, geo and extended data\n```/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
+ "description": "You can submit an event using an HTTP GET and query string parameters. Any unknown query string parameters will be added to the extended data of the event.\r\n\r\nFeature usage named build with a duration of 10:\r\n```/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\r\n\r\nLog with message, geo and extended data\r\n```/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
"parameters": [
{
"name": "type",
@@ -3681,7 +3681,7 @@
"Event"
],
"summary": "Submit event type by GET",
- "description": "You can submit an event using an HTTP GET and query string parameters.\n\nFeature usage event named build with a value of 10:\n```/events/submit/usage?access_token=YOUR_API_KEY&source=build&value=10```\n\nLog event with message, geo and extended data\n```/events/submit/log?access_token=YOUR_API_KEY&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
+ "description": "You can submit an event using an HTTP GET and query string parameters.\r\n\r\nFeature usage event named build with a value of 10:\r\n```/events/submit/usage?access_token=YOUR_API_KEY&source=build&value=10```\r\n\r\nLog event with message, geo and extended data\r\n```/events/submit/log?access_token=YOUR_API_KEY&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
"parameters": [
{
"name": "type",
@@ -3817,7 +3817,7 @@
"Event"
],
"summary": "Submit event type by GET for a specific project",
- "description": "You can submit an event using an HTTP GET and query string parameters.\n\nFeature usage named build with a duration of 10:\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\n\nLog with message, geo and extended data\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
+ "description": "You can submit an event using an HTTP GET and query string parameters.\r\n\r\nFeature usage named build with a duration of 10:\r\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\r\n\r\nLog with message, geo and extended data\r\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
"parameters": [
{
"name": "projectId",
@@ -3953,7 +3953,7 @@
"Event"
],
"summary": "Submit event type by GET for a specific project",
- "description": "You can submit an event using an HTTP GET and query string parameters.\n\nFeature usage named build with a duration of 10:\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\n\nLog with message, geo and extended data\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
+ "description": "You can submit an event using an HTTP GET and query string parameters.\r\n\r\nFeature usage named build with a duration of 10:\r\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=usage&source=build&value=10```\r\n\r\nLog with message, geo and extended data\r\n```/projects/{projectId}/events/submit?access_token=YOUR_API_KEY&type=log&message=Hello World&source=server01&geo=32.85,-96.9613&randomproperty=true```",
"parameters": [
{
"name": "projectId",
@@ -4683,7 +4683,7 @@
"Organization"
],
"summary": "Change plan",
- "description": "Upgrades or downgrades the organization's plan.\nAccepts parameters via JSON body (preferred) or query string (legacy).",
+ "description": "Upgrades or downgrades the organization's plan.\r\nAccepts parameters via JSON body (preferred) or query string (legacy).",
"parameters": [
{
"name": "id",
@@ -7929,6 +7929,18 @@
"type": "string"
}
},
+ "show_stats": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
+ "show_chart": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
"is_private": {
"type": [
"null",
@@ -8152,7 +8164,7 @@
"null",
"string"
],
- "description": "The event type (ie. error, log message, feature usage). Check KnownTypes for standard event types.\nNullable in transit; the pipeline infers a default before save. Validated as required on repository save."
+ "description": "The event type (ie. error, log message, feature usage). Check KnownTypes for standard event types.\r\nNullable in transit; the pipeline infers a default before save. Validated as required on repository save."
},
"source": {
"maxLength": 2000,
@@ -8583,9 +8595,25 @@
}
},
"column_order": {
+ "maxItems": 50,
"type": [
"null",
"array"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "show_stats": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
+ "show_chart": {
+ "type": [
+ "null",
+ "boolean"
]
}
},
@@ -9261,6 +9289,18 @@
"type": "string"
}
},
+ "show_stats": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
+ "show_chart": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
"name": {
"type": "string"
},