diff --git a/docs/admin-ui.md b/docs/admin-ui.md
index 2c9b8a9d04..c414b79bfe 100644
--- a/docs/admin-ui.md
+++ b/docs/admin-ui.md
@@ -16,7 +16,7 @@ The dashboard opens at `/ui` and combines the daily operational view in one plac
## Navigator
-The _Navigator_ page links to the main workspace areas: streams, queries, projections, subscriptions, users, operations, observability, and configuration.
+The _Navigator_ page links to the main workspace areas: streams, projections, subscriptions, users, operations, observability, and configuration.
## Observability
@@ -37,15 +37,11 @@ The _Streams_ page lets you find recently created or changed streams and open a
- paged event browsing
- event detail views with data and metadata
-- append, metadata, ACL, delete, and query actions
-
-## Query
-
-The _Query_ page runs transient JavaScript projections for short-lived analysis and can stop the generated transient projection from the same page.
+- append, metadata, ACL, and delete actions
## Projections
-The _Projections_ page shows user, system, and optionally transient projections. It supports creating projections, creating standard projections, bulk enable or disable, and opening a projection for inspection.
+The _Projections_ page shows user and system projections. It supports creating projections, creating standard projections, bulk enable or disable, and opening a projection for inspection.
Projection detail pages provide source, status, config, state, result, debug, edit, reset, and delete workflows. Projection features are available when projections are enabled on the node.
diff --git a/docs/projections.md b/docs/projections.md
index e75f6504e6..03bea3fb1d 100644
--- a/docs/projections.md
+++ b/docs/projections.md
@@ -514,8 +514,7 @@ Once the projection is running, open the Admin UI, go to _Projections_, select t
_Debug_. The debug page shows the current projection source, state payload, and status, with actions to stop or
reset the projection when the runtime state needs to be cleared.
-Use _Open in query_ to run the projection source as a transient query, or _Edit source_ to change the persistent
-projection definition.
+Use _Edit source_ to change the persistent projection definition.
## Projections settings
diff --git a/src/EventStore.ClusterNode/Components/Layout/NavMenu.razor b/src/EventStore.ClusterNode/Components/Layout/NavMenu.razor
index 7e9d87c67d..17fc82de6d 100644
--- a/src/EventStore.ClusterNode/Components/Layout/NavMenu.razor
+++ b/src/EventStore.ClusterNode/Components/Layout/NavMenu.razor
@@ -24,7 +24,6 @@
Data plane
Streams
- Query
Projections
Subscriptions
diff --git a/src/EventStore.ClusterNode/Components/Pages/Navigator.razor b/src/EventStore.ClusterNode/Components/Pages/Navigator.razor
index c67c344b13..d1f52e8c74 100644
--- a/src/EventStore.ClusterNode/Components/Pages/Navigator.razor
+++ b/src/EventStore.ClusterNode/Components/Pages/Navigator.razor
@@ -11,7 +11,6 @@
-
diff --git a/src/EventStore.ClusterNode/Components/Pages/ProjectionDebug.razor b/src/EventStore.ClusterNode/Components/Pages/ProjectionDebug.razor
index e2ca58bc1b..197646ac53 100644
--- a/src/EventStore.ClusterNode/Components/Pages/ProjectionDebug.razor
+++ b/src/EventStore.ClusterNode/Components/Pages/ProjectionDebug.razor
@@ -65,7 +65,6 @@
Reset
- Open in query
Edit source
@@ -105,7 +104,6 @@
private string LinkName => Page?.Name ?? Name;
private string DetailHref => $"/ui/projections/{Uri.EscapeDataString(LinkName)}";
private string EditHref => $"/ui/projections/edit/{Uri.EscapeDataString(LinkName)}";
- private string QueryHref => $"/ui/query?location={Uri.EscapeDataString(LinkName)}";
protected override async Task OnParametersSetAsync() {
DisableInput ??= new();
diff --git a/src/EventStore.ClusterNode/Components/Pages/Projections.razor b/src/EventStore.ClusterNode/Components/Pages/Projections.razor
index 1d07de8943..47a56dafdc 100644
--- a/src/EventStore.ClusterNode/Components/Pages/Projections.razor
+++ b/src/EventStore.ClusterNode/Components/Pages/Projections.razor
@@ -12,7 +12,6 @@
@@ -55,7 +54,7 @@
{
-
Showing @(Page.IncludeQueries ? "all projections including transient queries" : "non-transient projections").
+
Showing non-transient projections.
@@ -128,8 +127,6 @@
@code {
- [SupplyParameterFromQuery] public bool IncludeQueries { get; set; }
-
[SupplyParameterFromForm(FormName = "projection-enable-all")]
private ProjectionBulkInput EnableAllInput { get; set; }
@@ -138,8 +135,7 @@
private ProjectionListPage Page { get; set; }
private ProjectionBulkCommandResult CommandResult { get; set; }
- private string EmptyProjectionsMessage =>
- Page?.IncludeQueries == true ? "No projections are available." : "No non-transient projections are available.";
+ private const string EmptyProjectionsMessage = "No non-transient projections are available.";
protected override async Task OnParametersSetAsync() {
EnableAllInput ??= new();
@@ -149,7 +145,7 @@
try
{
- Page = await ProjectionBrowser.ReadAll(IncludeQueries);
+ Page = await ProjectionBrowser.ReadAll();
}
catch (OperationCanceledException)
{
@@ -157,13 +153,13 @@
}
catch (Exception ex)
{
- Page = ProjectionListPage.Unavailable($"Failed to load projections: {UiMessages.Friendly(ex)}", IncludeQueries);
+ Page = ProjectionListPage.Unavailable($"Failed to load projections: {UiMessages.Friendly(ex)}");
}
}
private async Task EnableAll() {
try {
- CommandResult = await ProjectionBrowser.EnableAll(IncludeQueries);
+ CommandResult = await ProjectionBrowser.EnableAll();
} catch (OperationCanceledException) {
CommandResult = ProjectionBulkCommandResult.Failure("Enable projections was canceled.");
return;
@@ -177,7 +173,7 @@
private async Task DisableAll() {
try {
- CommandResult = await ProjectionBrowser.DisableAll(IncludeQueries);
+ CommandResult = await ProjectionBrowser.DisableAll();
} catch (OperationCanceledException) {
CommandResult = ProjectionBulkCommandResult.Failure("Disable projections was canceled.");
return;
@@ -191,11 +187,11 @@
private async Task RefreshAfterBulkAction() {
try {
- Page = await ProjectionBrowser.ReadAll(IncludeQueries);
+ Page = await ProjectionBrowser.ReadAll();
} catch (OperationCanceledException) {
- Page = ProjectionListPage.Unavailable("Projection command completed, but refresh was canceled.", IncludeQueries);
+ Page = ProjectionListPage.Unavailable("Projection command completed, but refresh was canceled.");
} catch (Exception ex) {
- Page = ProjectionListPage.Unavailable($"Projection command completed, but refresh failed: {UiMessages.Friendly(ex)}", IncludeQueries);
+ Page = ProjectionListPage.Unavailable($"Projection command completed, but refresh failed: {UiMessages.Friendly(ex)}");
}
}
diff --git a/src/EventStore.ClusterNode/Components/Pages/Query.razor b/src/EventStore.ClusterNode/Components/Pages/Query.razor
deleted file mode 100644
index fe50255490..0000000000
--- a/src/EventStore.ClusterNode/Components/Pages/Query.razor
+++ /dev/null
@@ -1,211 +0,0 @@
-@page "/ui/query"
-@inject QueryBrowserService QueryBrowser
-@inject ProjectionBrowserService Projections
-@using System.ComponentModel.DataAnnotations
-@using System.Security.Cryptography
-@using Microsoft.AspNetCore.DataProtection
-@using Microsoft.AspNetCore.Components.Forms
-@inject IDataProtectionProvider DataProtectionProvider
-
-Query - EventStore UI
-
-
-
-
Query
-
Run transient projection queries with the guardrails visible.
-
Submit JavaScript projection queries and inspect the created transient projection from the same page.
-
-
-
Execution model
-
Queries run as transient projections under the current authenticated user. They are suitable for inspection workflows, not as a substitute for permanent projection management.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Query text
-
-
-
-
-
-
-
- @if (CommandResult is not null)
- {
-
@CommandResult.Message
- }
- @if (Result is null)
- {
-
- Run a query to create a transient projection and reveal result links.
-
- }
- else if (!Result.IsAvailable)
- {
-
- @Result.Message
-
- }
- else
- {
-
-
-
-
-
- @Result.ProjectionName
- The projection may still be processing. Open its detail or debug page to inspect the latest server-side view.
-
-
- }
-
-
-
-@code {
- private const string DefaultQuery = "fromAll().when({\n $init: function () { return { count: 0 }; },\n $any: function (state, event) {\n state.count += 1;\n }\n});";
-
- [SupplyParameterFromQuery] public string InitStreamId { get; set; } = "";
- [SupplyParameterFromQuery] public string Location { get; set; } = "";
-
- [SupplyParameterFromForm(FormName = "transient-query")]
- private QueryInput Input { get; set; }
-
- [SupplyParameterFromForm(FormName = "query-stop")]
- private QueryStopInput StopInput { get; set; }
-
- private QueryRunPage Result { get; set; }
- private ProjectionCommandResult CommandResult { get; set; }
- private string _lastSeededQuery = "";
- private string _lastLoadedLocation = "";
- private IDataProtector StopTargetProtector =>
- DataProtectionProvider.CreateProtector("EventStore.ClusterNode.Components.Pages.Query.StopProjection");
-
- protected override async Task OnParametersSetAsync() {
- Input ??= new() { Query = DefaultQuery };
- StopInput ??= new();
-
- if (!string.IsNullOrWhiteSpace(Location)) {
- if (string.Equals(_lastLoadedLocation, Location, StringComparison.OrdinalIgnoreCase) ||
- (string.IsNullOrWhiteSpace(_lastLoadedLocation) && !CanSeedQuery()))
- return;
-
- try {
- var page = await Projections.ReadProjectionQuery(Location);
- if (page.Query is not null) {
- Input.Query = page.Query.Query;
- _lastSeededQuery = page.Query.Query;
- _lastLoadedLocation = Location;
- Result = QueryRunPage.Success(Input.Query, page.Query.Name);
- StopInput.StopToken = ProtectStopTarget(page.Query.Name);
- } else {
- Result = QueryRunPage.Unavailable(Input.Query, page.Message);
- }
- } catch (OperationCanceledException) {
- throw;
- } catch (Exception ex) {
- Result = QueryRunPage.Unavailable(Input.Query, $"Failed to load query projection: {UiMessages.Friendly(ex)}");
- }
-
- return;
- }
-
- if (string.IsNullOrWhiteSpace(InitStreamId)) {
- if (!string.IsNullOrWhiteSpace(_lastSeededQuery) &&
- string.Equals(Input.Query, _lastSeededQuery, StringComparison.Ordinal))
- Input.Query = DefaultQuery;
-
- _lastSeededQuery = "";
- _lastLoadedLocation = "";
- return;
- }
-
- var seed = StreamQuery(InitStreamId);
- if (!string.Equals(Input.Query, DefaultQuery, StringComparison.Ordinal) &&
- !string.Equals(Input.Query, _lastSeededQuery, StringComparison.Ordinal))
- return;
-
- Input.Query = seed;
- _lastSeededQuery = seed;
- }
-
- private async Task RunQuery() {
- try {
- Result = await QueryBrowser.RunTransient(Input.Query);
- StopInput.StopToken = ProtectStopTarget(Result.ProjectionName);
- CommandResult = null;
- } catch (OperationCanceledException) {
- Result = QueryRunPage.Unavailable(Input.Query, "Query execution was canceled.");
- } catch (Exception ex) {
- Result = QueryRunPage.Unavailable(Input.Query, $"Failed to run query: {UiMessages.Friendly(ex)}");
- }
- }
-
- private async Task StopProjection() {
- if (!TryReadStopTarget(out var projectionName)) {
- CommandResult = ProjectionCommandResult.Failure("", "Run or load a query projection before stopping it.");
- return;
- }
-
- Result = QueryRunPage.Success(Input.Query, projectionName);
- StopInput.StopToken = ProtectStopTarget(projectionName);
- try {
- CommandResult = await Projections.Disable(projectionName);
- } catch (OperationCanceledException) {
- CommandResult = ProjectionCommandResult.Failure(projectionName, "Query break was canceled.");
- } catch (Exception ex) {
- CommandResult = ProjectionCommandResult.Failure(projectionName, $"Failed to stop query projection: {UiMessages.Friendly(ex)}");
- }
- }
-
- private static string StreamQuery(string streamId) =>
- $"fromStream({System.Text.Json.JsonSerializer.Serialize(streamId)}).when({{\n $init: function () {{ return {{ count: 0 }}; }},\n $any: function (state, event) {{\n state.count += 1;\n }}\n}});";
-
- private bool CanSeedQuery() =>
- string.Equals(Input.Query, DefaultQuery, StringComparison.Ordinal) ||
- string.Equals(Input.Query, _lastSeededQuery, StringComparison.Ordinal);
-
- private string ProtectStopTarget(string projectionName) =>
- string.IsNullOrWhiteSpace(projectionName) ? "" : StopTargetProtector.Protect(projectionName);
-
- private bool TryReadStopTarget(out string projectionName) {
- projectionName = "";
- if (string.IsNullOrWhiteSpace(StopInput.StopToken))
- return false;
-
- try {
- projectionName = StopTargetProtector.Unprotect(StopInput.StopToken);
- return !string.IsNullOrWhiteSpace(projectionName);
- } catch (CryptographicException) {
- return false;
- }
- }
-
- private sealed class QueryInput {
- [Required(ErrorMessage = "Enter a query before running it.")]
- public string Query { get; set; } = "";
- }
-
- private sealed class QueryStopInput {
- public string StopToken { get; set; } = "";
- }
-}
diff --git a/src/EventStore.ClusterNode/Components/Pages/StreamDetail.razor b/src/EventStore.ClusterNode/Components/Pages/StreamDetail.razor
index 8dba8608c4..5215c260fe 100644
--- a/src/EventStore.ClusterNode/Components/Pages/StreamDetail.razor
+++ b/src/EventStore.ClusterNode/Components/Pages/StreamDetail.razor
@@ -23,7 +23,6 @@
{
Delete
}
- Query
diff --git a/src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs b/src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
index 10704dd56a..8fa8c29cd6 100644
--- a/src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
+++ b/src/EventStore.ClusterNode/Components/Services/ProjectionBrowserService.cs
@@ -58,19 +58,18 @@ public sealed class ProjectionBrowserService(
"Reading Speed Test Handler")
};
- public async Task
ReadAll(bool includeQueries, CancellationToken cancellationToken = default)
+ public async Task ReadAll(CancellationToken cancellationToken = default)
{
if (!await HasAccess(ListOperation, cancellationToken))
{
- return ProjectionListPage.Unavailable("Projection list access was denied.", includeQueries);
+ return ProjectionListPage.Unavailable("Projection list access was denied.");
}
- var read = await ReadStatistics(includeQueries ? null : ProjectionMode.AllNonTransient, name: null, cancellationToken);
+ var read = await ReadStatistics(ProjectionMode.AllNonTransient, name: null, cancellationToken);
return read.IsAvailable
? ProjectionListPage.Success(
- read.Projections.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToArray(),
- includeQueries)
- : ProjectionListPage.Unavailable(read.Message, includeQueries);
+ read.Projections.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToArray())
+ : ProjectionListPage.Unavailable(read.Message);
}
public async Task ReadProjection(
@@ -294,9 +293,9 @@ public Task Reset(string name, CancellationToken cancel
new ProjectionManagementMessage.RunAs(CurrentUser)),
cancellationToken);
- public async Task EnableAll(bool includeQueries, CancellationToken cancellationToken = default)
+ public async Task EnableAll(CancellationToken cancellationToken = default)
{
- var page = await ReadAll(includeQueries, cancellationToken);
+ var page = await ReadAll(cancellationToken);
if (!page.IsAvailable)
{
return ProjectionBulkCommandResult.Failure(page.Message);
@@ -306,9 +305,9 @@ public async Task EnableAll(bool includeQueries, Ca
return await RunBulk(candidates, projection => Enable(projection.Name, cancellationToken), "enable");
}
- public async Task DisableAll(bool includeQueries, CancellationToken cancellationToken = default)
+ public async Task DisableAll(CancellationToken cancellationToken = default)
{
- var page = await ReadAll(includeQueries, cancellationToken);
+ var page = await ReadAll(cancellationToken);
if (!page.IsAvailable)
{
return ProjectionBulkCommandResult.Failure(page.Message);
@@ -799,7 +798,6 @@ public static ProjectionDataRead Unavailable(string message) =>
public sealed record ProjectionListPage(
IReadOnlyList Projections,
- bool IncludeQueries,
string Message)
{
public bool IsAvailable => string.IsNullOrWhiteSpace(Message);
@@ -810,14 +808,12 @@ public sealed record ProjectionListPage(
public string RunningCountLabel => IsAvailable ? RunningCount.ToString() : "-";
public string FaultedCountLabel => IsAvailable ? FaultedCount.ToString() : "-";
public string DisabledCountLabel => IsAvailable ? DisabledCount.ToString() : "-";
- public string ToggleQueriesHref => IncludeQueries ? "/ui/projections" : "/ui/projections?includeQueries=true";
- public string ToggleQueriesLabel => IncludeQueries ? "Hide transient queries" : "Include transient queries";
- public static ProjectionListPage Success(IReadOnlyList projections, bool includeQueries) =>
- new(projections, includeQueries, "");
+ public static ProjectionListPage Success(IReadOnlyList projections) =>
+ new(projections, "");
- public static ProjectionListPage Unavailable(string message, bool includeQueries = false) =>
- new(Array.Empty(), includeQueries, message);
+ public static ProjectionListPage Unavailable(string message) =>
+ new(Array.Empty(), message);
}
public sealed record ProjectionDetailPage(
diff --git a/src/EventStore.ClusterNode/Components/Services/QueryBrowserService.cs b/src/EventStore.ClusterNode/Components/Services/QueryBrowserService.cs
deleted file mode 100644
index 5dd5f807c3..0000000000
--- a/src/EventStore.ClusterNode/Components/Services/QueryBrowserService.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Security.Claims;
-using System.Threading;
-using System.Threading.Tasks;
-using EventStore.Core.Bus;
-using EventStore.Core.Messaging;
-using EventStore.Plugins.Authorization;
-using EventStore.Projections.Core.Messages;
-using EventStore.Projections.Core.Services;
-using Microsoft.AspNetCore.Http;
-
-namespace EventStore.ClusterNode.Components.Services;
-
-public sealed class QueryBrowserService(
- IPublisher publisher,
- IAuthorizationProvider authorizationProvider,
- IHttpContextAccessor httpContextAccessor)
-{
- private static readonly TimeSpan ReadTimeout = TimeSpan.FromSeconds(10);
- private static readonly Operation CreateQueryOperation = new Operation(Operations.Projections.Create)
- .WithParameter(Operations.Projections.Parameters.Query);
-
- public async Task RunTransient(string query, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrWhiteSpace(query))
- {
- return QueryRunPage.Unavailable(query ?? "", "Enter a query before running it.");
- }
-
- if (!await HasAccess(CreateQueryOperation, cancellationToken))
- {
- return QueryRunPage.Unavailable(query, "Transient query access was denied.");
- }
-
- var projectionName = Guid.NewGuid().ToString("D");
- var envelope = new TaskCompletionEnvelope(
- mapFailure: message => message is ProjectionManagementMessage.OperationFailed failed ? failed.Reason ?? "" : null);
-
- ProjectionManagementMessage.Updated completed;
- try
- {
- publisher.Publish(new ProjectionManagementMessage.Command.Post(
- envelope,
- ProjectionMode.Transient,
- projectionName,
- new ProjectionManagementMessage.RunAs(CurrentUser),
- "JS",
- query,
- enabled: true,
- checkpointsEnabled: false,
- emitEnabled: false,
- trackEmittedStreams: false,
- enableRunAs: true));
-
- completed = await envelope.Task.WaitAsync(ReadTimeout, cancellationToken);
- }
- catch (TimeoutException)
- {
- return QueryRunPage.Unavailable(query,
- $"Timed out creating transient query '{projectionName}'. It may still be visible temporarily before automatic expiry.");
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- return QueryRunPage.Unavailable(query, $"Unable to create transient query: {UiMessages.Friendly(ex)}");
- }
-
- return QueryRunPage.Success(query, completed.Name);
- }
-
- private ClaimsPrincipal CurrentUser =>
- httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity());
-
- private Task HasAccess(Operation operation, CancellationToken cancellationToken) =>
- authorizationProvider.CheckAccessAsync(CurrentUser, operation, cancellationToken).AsTask();
-
-}
-
-public sealed record QueryRunPage(
- string Query,
- string ProjectionName,
- string Message)
-{
- public bool IsAvailable => string.IsNullOrWhiteSpace(Message);
- public bool HasProjection => !string.IsNullOrWhiteSpace(ProjectionName);
- public string ProjectionHref => HasProjection ? $"/ui/projections/{Uri.EscapeDataString(ProjectionName)}" : "";
- public string DebugHref => HasProjection ? $"/ui/projections/debug/{Uri.EscapeDataString(ProjectionName)}" : "";
-
- public static QueryRunPage Success(string query, string projectionName) => new(query, projectionName, "");
- public static QueryRunPage Unavailable(string query, string message) => new(query, "", message);
-}
diff --git a/src/EventStore.ClusterNode/Program.cs b/src/EventStore.ClusterNode/Program.cs
index e8f181a341..4265968fc7 100644
--- a/src/EventStore.ClusterNode/Program.cs
+++ b/src/EventStore.ClusterNode/Program.cs
@@ -276,7 +276,6 @@ async Task Run(ClusterVNodeHostedService hostedService, ManualResetEventSlim sig
builder.Services.AddHttpContextAccessor();
builder.Services.AddRazorComponents();
builder.Services.AddScoped();
- builder.Services.AddScoped();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();