((set) => ({
export const useCooldownActions = () => cooldownStore((state) => state.actions)
-export const useCooldownState = (domain: ModalMode) =>
+export const useCooldownState = (domain: CooldownDomain) =>
cooldownStore((state) => state.cooldowns[domain])
export { cooldownStore }
-export type { ModalMode } from './modalmodes'
diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/errorHandling.tsx b/src/Elastic.Documentation.Site/Assets/web-components/shared/errorHandling.tsx
similarity index 100%
rename from src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/errorHandling.tsx
rename to src/Elastic.Documentation.Site/Assets/web-components/shared/errorHandling.tsx
diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/useRateLimitHandler.ts b/src/Elastic.Documentation.Site/Assets/web-components/shared/useRateLimitHandler.ts
similarity index 97%
rename from src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/useRateLimitHandler.ts
rename to src/Elastic.Documentation.Site/Assets/web-components/shared/useRateLimitHandler.ts
index ddfe2fda5..eec3a593e 100644
--- a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/useRateLimitHandler.ts
+++ b/src/Elastic.Documentation.Site/Assets/web-components/shared/useRateLimitHandler.ts
@@ -1,7 +1,7 @@
import {
useCooldownState,
useCooldownActions,
- ModalMode,
+ CooldownDomain,
} from './cooldown.store'
import { ApiError, isRateLimitError, isApiError } from './errorHandling'
import { useEffect, useRef } from 'react'
@@ -12,7 +12,7 @@ import { useEffect, useRef } from 'react'
* @param error - The error to check for rate limiting
*/
export function useRateLimitHandler(
- domain: ModalMode,
+ domain: CooldownDomain,
error: ApiError | Error | null
) {
const state = useCooldownState(domain)
diff --git a/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml b/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml
index 2f66543c7..3800fd23e 100644
--- a/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml
+++ b/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml
@@ -5,7 +5,8 @@
@{
var currentTopLevelItem = Model.TopLevelItems.FirstOrDefault(i => i.Id == Model.Tree.Id) ?? Model.Tree;
}
-
+
+
@if (Model.IsUsingNavigationDropdown)
{
diff --git a/src/api/Elastic.Documentation.Api.Core/Search/ISearchGateway.cs b/src/api/Elastic.Documentation.Api.Core/Search/IFindPageGateway.cs
similarity index 75%
rename from src/api/Elastic.Documentation.Api.Core/Search/ISearchGateway.cs
rename to src/api/Elastic.Documentation.Api.Core/Search/IFindPageGateway.cs
index 938802d3e..f096eec26 100644
--- a/src/api/Elastic.Documentation.Api.Core/Search/ISearchGateway.cs
+++ b/src/api/Elastic.Documentation.Api.Core/Search/IFindPageGateway.cs
@@ -4,9 +4,9 @@
namespace Elastic.Documentation.Api.Core.Search;
-public interface ISearchGateway
+public interface IFindPageGateway
{
- Task SearchAsync(
+ Task FindPageAsync(
string query,
int pageNumber,
int pageSize,
@@ -15,9 +15,9 @@ Task SearchAsync(
);
}
-public record SearchResult
+public record FindPageResult
{
public required int TotalHits { get; init; }
- public required List Results { get; init; }
+ public required List Results { get; init; }
public IReadOnlyDictionary Aggregations { get; init; } = new Dictionary();
}
diff --git a/src/api/Elastic.Documentation.Api.Core/Search/SearchUsecase.cs b/src/api/Elastic.Documentation.Api.Core/Search/SearchUsecase.cs
index 5b4834a91..912825d1c 100644
--- a/src/api/Elastic.Documentation.Api.Core/Search/SearchUsecase.cs
+++ b/src/api/Elastic.Documentation.Api.Core/Search/SearchUsecase.cs
@@ -6,11 +6,12 @@
namespace Elastic.Documentation.Api.Core.Search;
-public partial class SearchUsecase(ISearchGateway searchGateway, ILogger logger)
+// note still called SearchUseCase because we'll re-add Search() and ensure both share the same client.
+public partial class SearchUsecase(IFindPageGateway findPageGateway, ILogger logger)
{
- public async Task Search(SearchApiRequest request, Cancel ctx = default)
+ public async Task FindPageAsync(FindPageApiRequest request, Cancel ctx = default)
{
- var searchResult = await searchGateway.SearchAsync(
+ var result = await findPageGateway.FindPageAsync(
request.Query,
request.PageNumber,
request.PageSize,
@@ -18,33 +19,33 @@ public async Task Search(SearchApiRequest request, Cancel ctx
ctx
);
- var response = new SearchApiResponse
+ var response = new FindPageApiResponse
{
- Results = searchResult.Results,
- TotalResults = searchResult.TotalHits,
+ Results = result.Results,
+ TotalResults = result.TotalHits,
PageNumber = request.PageNumber,
PageSize = request.PageSize,
- Aggregations = new SearchAggregations { Type = searchResult.Aggregations }
+ Aggregations = new FindPageAggregations { Type = result.Aggregations }
};
- LogSearchResults(
+ LogFindPageResults(
logger,
response.PageSize,
response.PageNumber,
request.Query,
- new SearchResultsLogProperties(searchResult.Results.Select(i => i.Url).ToArray())
+ new AutoCompleteResultsLogProperties(result.Results.Select(i => i.Url).ToArray())
);
return response;
}
- [LoggerMessage(Level = LogLevel.Information, Message = "Search completed with {PageSize} (page {PageNumber}) results for query '{SearchQuery}'")]
- private static partial void LogSearchResults(ILogger logger, int pageSize, int pageNumber, string searchQuery, [LogProperties] SearchResultsLogProperties result);
+ [LoggerMessage(Level = LogLevel.Information, Message = "Find page completed with {PageSize} (page {PageNumber}) results for query '{SearchQuery}'")]
+ private static partial void LogFindPageResults(ILogger logger, int pageSize, int pageNumber, string searchQuery, [LogProperties] AutoCompleteResultsLogProperties result);
- private sealed record SearchResultsLogProperties(string[] Urls);
+ private sealed record AutoCompleteResultsLogProperties(string[] Urls);
}
-public record SearchApiRequest
+public record FindPageApiRequest
{
public required string Query { get; init; }
public int PageNumber { get; init; } = 1;
@@ -52,35 +53,35 @@ public record SearchApiRequest
public string? TypeFilter { get; init; }
}
-public record SearchApiResponse
+public record FindPageApiResponse
{
- public required IEnumerable Results { get; init; }
+ public required IEnumerable Results { get; init; }
public required int TotalResults { get; init; }
public required int PageNumber { get; init; }
public required int PageSize { get; init; }
- public SearchAggregations Aggregations { get; init; } = new();
+ public FindPageAggregations Aggregations { get; init; } = new();
public int PageCount => TotalResults > 0
? (int)Math.Ceiling((double)TotalResults / PageSize)
: 0;
}
-public record SearchAggregations
+public record FindPageAggregations
{
public IReadOnlyDictionary Type { get; init; } = new Dictionary();
}
-public record SearchResultItemParent
+public record FindPageResultItemParent
{
public required string Title { get; init; }
public required string Url { get; init; }
}
-public record SearchResultItem
+public record FindPageResultItem
{
public required string Type { get; init; }
public required string Url { get; init; }
public required string Title { get; init; }
public required string Description { get; init; }
- public required SearchResultItemParent[] Parents { get; init; }
+ public required FindPageResultItemParent[] Parents { get; init; }
public float Score { get; init; }
}
diff --git a/src/api/Elastic.Documentation.Api.Core/SerializationContext.cs b/src/api/Elastic.Documentation.Api.Core/SerializationContext.cs
index 93f614b10..0904e0eef 100644
--- a/src/api/Elastic.Documentation.Api.Core/SerializationContext.cs
+++ b/src/api/Elastic.Documentation.Api.Core/SerializationContext.cs
@@ -20,9 +20,9 @@ public record OutputMessage(string Role, MessagePart[] Parts, string FinishReaso
[JsonSerializable(typeof(AskAiRequest))]
[JsonSerializable(typeof(AskAiMessageFeedbackRequest))]
[JsonSerializable(typeof(Reaction))]
-[JsonSerializable(typeof(SearchApiRequest))]
-[JsonSerializable(typeof(SearchApiResponse))]
-[JsonSerializable(typeof(SearchAggregations))]
+[JsonSerializable(typeof(FindPageApiRequest))]
+[JsonSerializable(typeof(FindPageApiResponse))]
+[JsonSerializable(typeof(FindPageAggregations))]
[JsonSerializable(typeof(InputMessage))]
[JsonSerializable(typeof(OutputMessage[]))]
[JsonSerializable(typeof(MessagePart))]
diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/ElasticsearchGateway.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/ElasticsearchGateway.cs
index 815fe1d0d..c52b32c35 100644
--- a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/ElasticsearchGateway.cs
+++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/ElasticsearchGateway.cs
@@ -19,7 +19,7 @@
namespace Elastic.Documentation.Api.Infrastructure.Adapters.Search;
-public partial class ElasticsearchGateway : ISearchGateway
+public partial class ElasticsearchGateway : IFindPageGateway
{
private readonly ElasticsearchClient _client;
private readonly ElasticsearchOptions _elasticsearchOptions;
@@ -55,7 +55,7 @@ public ElasticsearchGateway(ElasticsearchOptions elasticsearchOptions, SearchCon
public async Task CanConnect(Cancel ctx) => (await _client.PingAsync(ctx)).IsValidResponse;
- public async Task SearchAsync(string query, int pageNumber, int pageSize, string? filter = null, Cancel ctx = default) =>
+ public async Task FindPageAsync(string query, int pageNumber, int pageSize, string? filter = null, Cancel ctx = default) =>
await SearchImplementation(query, pageNumber, pageSize, filter, ctx);
///
@@ -238,7 +238,7 @@ private static Query BuildSemanticQuery(string searchQuery) =>
new TermsQueryField(["/docs", "/docs/", "/docs/404", "/docs/404/"]))
&& !(Query)new TermQuery { Field = Infer.Field(f => f.Hidden), Value = true };
- public async Task SearchImplementation(string query, int pageNumber, int pageSize, string? filter = null, Cancel ctx = default)
+ public async Task SearchImplementation(string query, int pageNumber, int pageSize, string? filter = null, Cancel ctx = default)
{
const string preTag = "";
const string postTag = "";
@@ -318,7 +318,7 @@ public async Task SearchImplementation(string query, int pageNumbe
}
}
- private static SearchResult ProcessSearchResponse(
+ private static FindPageResult ProcessSearchResponse(
SearchResponse response,
string searchQuery,
IReadOnlyDictionary synonyms)
@@ -351,13 +351,13 @@ private static SearchResult ProcessSearchResponse(
.Trim(['|', ' '])
.HighlightTokens(searchTokens, synonyms);
- return new SearchResultItem
+ return new FindPageResultItem
{
Url = doc.Url,
Title = title,
Type = doc.Type,
Description = description,
- Parents = doc.Parents.Select(parent => new SearchResultItemParent
+ Parents = doc.Parents.Select(parent => new FindPageResultItemParent
{
Title = parent.Title,
Url = parent.Url
@@ -375,7 +375,7 @@ private static SearchResult ProcessSearchResponse(
aggregations[bucket.Key.ToString()] = bucket.DocCount;
}
- return new SearchResult
+ return new FindPageResult
{
TotalHits = totalHits,
Results = results,
diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/MockSearchGateway.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/MockSearchGateway.cs
index 1cc498020..169ed3fb7 100644
--- a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/MockSearchGateway.cs
+++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/Search/MockSearchGateway.cs
@@ -6,11 +6,11 @@
namespace Elastic.Documentation.Api.Infrastructure.Adapters.Search;
-public class MockSearchGateway : ISearchGateway
+public class MockFindPageGateway : IFindPageGateway
{
- private static readonly List Results =
+ private static readonly List Results =
[
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/kibana",
@@ -19,7 +19,7 @@ public class MockSearchGateway : ISearchGateway
"Run data analytics at speed and scale for observability, security, and search with Kibana. Powerful analysis on any data from any source.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs/explore-analyze",
@@ -27,7 +27,7 @@ public class MockSearchGateway : ISearchGateway
Description = "Kibana provides a comprehensive suite of tools to help you search, interact with, explore, and analyze your data effectively.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs/deploy-manage/deploy/self-managed/install-kibana",
@@ -36,7 +36,7 @@ public class MockSearchGateway : ISearchGateway
"Information on how to set up Kibana and get it running, including downloading, enrollment with Elasticsearch cluster, and configuration.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/kibana/kibana-lens",
@@ -45,7 +45,7 @@ public class MockSearchGateway : ISearchGateway
"Kibana Lens simplifies the process of data visualization through a drag‑and‑drop experience, ideal for exploring logs, trends, and metrics.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs",
@@ -54,7 +54,7 @@ public class MockSearchGateway : ISearchGateway
"Official Elastic documentation. Explore guides for Elastic Cloud (hosted & on‑prem), product documentation, how‑to guides and API reference.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs/get-started/introduction",
@@ -63,7 +63,7 @@ public class MockSearchGateway : ISearchGateway
"Use Elasticsearch to search, index, store, and analyze data of all shapes and sizes in near real time. Kibana is the graphical user interface for Elasticsearch.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs/solutions/search/elasticsearch-basics-quickstart",
@@ -71,7 +71,7 @@ public class MockSearchGateway : ISearchGateway
Description = "Hands‑on introduction to fundamental Elasticsearch concepts: indices, documents, mappings, and search via Console syntax.",
Parents = []
},
- new SearchResultItem
+ new FindPageResultItem
{
Type = "doc",
Url = "https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-document",
@@ -82,7 +82,7 @@ public class MockSearchGateway : ISearchGateway
}
];
- public async Task SearchAsync(string query, int pageNumber, int pageSize, string? filter = null, CancellationToken ctx = default)
+ public async Task FindPageAsync(string query, int pageNumber, int pageSize, string? filter = null, CancellationToken ctx = default)
{
var filteredResults = Results
.Where(item =>
@@ -110,7 +110,7 @@ public async Task SearchAsync(string query, int pageNumber, int pa
Console.WriteLine($"MockSearchGateway: Paged results count: {pagedResults.Count}");
await Task.Delay(1000, ctx);
- return new SearchResult
+ return new FindPageResult
{
TotalHits = filteredResults.Count,
Results = pagedResults,
diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/MappingsExtensions.cs b/src/api/Elastic.Documentation.Api.Infrastructure/MappingsExtensions.cs
index 73dd2cd09..7a69179cb 100644
--- a/src/api/Elastic.Documentation.Api.Infrastructure/MappingsExtensions.cs
+++ b/src/api/Elastic.Documentation.Api.Infrastructure/MappingsExtensions.cs
@@ -19,7 +19,7 @@ public static void MapElasticDocsApiEndpoints(this IEndpointRouteBuilder group)
_ = group.MapGet("/", () => Results.Empty);
_ = group.MapPost("/", () => Results.Empty);
MapAskAiEndpoint(group);
- MapSearchEndpoint(group);
+ MapNavigationSearch(group);
MapOtlpProxyEndpoint(group);
}
@@ -47,9 +47,9 @@ private static void MapAskAiEndpoint(IEndpointRouteBuilder group)
}).DisableAntiforgery();
}
- private static void MapSearchEndpoint(IEndpointRouteBuilder group)
+ private static void MapNavigationSearch(IEndpointRouteBuilder group)
{
- var searchGroup = group.MapGroup("/search");
+ var searchGroup = group.MapGroup("/navigation-search");
_ = searchGroup.MapGet("/",
async (
[FromQuery(Name = "q")] string query,
@@ -59,14 +59,14 @@ private static void MapSearchEndpoint(IEndpointRouteBuilder group)
Cancel ctx
) =>
{
- var searchRequest = new SearchApiRequest
+ var request = new FindPageApiRequest
{
Query = query,
PageNumber = pageNumber ?? 1,
TypeFilter = typeFilter
};
- var searchResponse = await searchUsecase.Search(searchRequest, ctx);
- return Results.Ok(searchResponse);
+ var response = await searchUsecase.FindPageAsync(request, ctx);
+ return Results.Ok(response);
});
}
diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs b/src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs
index b3ab979ca..821b822b1 100644
--- a/src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs
+++ b/src/api/Elastic.Documentation.Api.Infrastructure/ServicesExtension.cs
@@ -233,7 +233,7 @@ private static void AddSearchUsecase(IServiceCollection services, AppEnv appEnv)
var logger = GetLogger(services);
logger?.LogInformation("Configuring Search use case for environment {AppEnvironment}", appEnv);
_ = services.AddSingleton();
- _ = services.AddScoped();
+ _ = services.AddScoped();
_ = services.AddScoped();
}
diff --git a/src/api/Elastic.Documentation.Api.Lambda/Program.cs b/src/api/Elastic.Documentation.Api.Lambda/Program.cs
index 1695b6c59..a31227d91 100644
--- a/src/api/Elastic.Documentation.Api.Lambda/Program.cs
+++ b/src/api/Elastic.Documentation.Api.Lambda/Program.cs
@@ -66,9 +66,9 @@
[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))]
[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))]
[JsonSerializable(typeof(AskAiRequest))]
-[JsonSerializable(typeof(SearchApiRequest))]
-[JsonSerializable(typeof(SearchApiResponse))]
-[JsonSerializable(typeof(SearchAggregations))]
+[JsonSerializable(typeof(FindPageApiRequest))]
+[JsonSerializable(typeof(FindPageApiResponse))]
+[JsonSerializable(typeof(FindPageAggregations))]
internal sealed partial class LambdaJsonSerializerContext : JsonSerializerContext;
// Make the Program class accessible for integration testing
diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/Search/SearchIntegrationTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/Search/SearchIntegrationTests.cs
index a06257526..0a748740f 100644
--- a/tests-integration/Elastic.Assembler.IntegrationTests/Search/SearchIntegrationTests.cs
+++ b/tests-integration/Elastic.Assembler.IntegrationTests/Search/SearchIntegrationTests.cs
@@ -55,7 +55,7 @@ public async Task SearchEndpointReturnsExpectedFirstResult(string query, string
// Assert - Response should be successful
response.EnsureSuccessStatusCode();
- var searchResponse = await response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
+ var searchResponse = await response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
searchResponse.Should().NotBeNull("Search response should be deserialized");
// Log results for debugging
@@ -93,12 +93,12 @@ public async Task SearchEndpointWithPaginationReturnsCorrectPage()
// Act - Get first page
var page1Response = await searchFixture.HttpClient!.GetAsync($"/docs/_api/v1/search?q={Uri.EscapeDataString(query)}&page=1", TestContext.Current.CancellationToken);
page1Response.EnsureSuccessStatusCode();
- var page1Data = await page1Response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
+ var page1Data = await page1Response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
// Act - Get second page
var page2Response = await searchFixture.HttpClient.GetAsync($"/docs/_api/v1/search?q={Uri.EscapeDataString(query)}&page=2", TestContext.Current.CancellationToken);
page2Response.EnsureSuccessStatusCode();
- var page2Data = await page2Response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
+ var page2Data = await page2Response.Content.ReadFromJsonAsync(cancellationToken: TestContext.Current.CancellationToken);
// Assert
page1Data.Should().NotBeNull();