feat(elasticsearch): add mapping, settings, stats and clear actions
This commit is contained in:
1 parent
a84cb637ba
commit
8f3134dce8
32 files changed
+1262
-3
No files matched your search
+58
@@ -0,0 +1,58 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const tauri = readFileSync(new URL("../tauri.ts", import.meta.url), "utf8");
|
||||
const http = readFileSync(new URL("../http.ts", import.meta.url), "utf8");
|
||||
const api = readFileSync(new URL("../api.ts", import.meta.url), "utf8");
|
||||
const tauriRegistry = readFileSync(new URL("../../../../../../src-tauri/src/lib.rs", import.meta.url), "utf8");
|
||||
const webRegistry = readFileSync(new URL("../../../../../../crates/dbx-web/src/main.rs", import.meta.url), "utf8");
|
||||
const coreOps = readFileSync(new URL("../../../../../../crates/dbx-core/src/document_ops.rs", import.meta.url), "utf8");
|
||||
|
||||
function functionBody(source: string, operation: string): string {
|
||||
const start = source.indexOf(`export async function ${operation}(`);
|
||||
expect(start, `${operation} transport function`).toBeGreaterThanOrEqual(0);
|
||||
const end = source.indexOf("\nexport async function ", start + 1);
|
||||
return source.slice(start, end === -1 ? source.length : end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nothing type-checks a frontend command name against its Rust registration, so
|
||||
* a rename on either side would ship a menu whose actions all fail at runtime.
|
||||
*/
|
||||
const operations = [
|
||||
{ name: "elasticsearchGetIndexMetadata", command: "elasticsearch_get_index_metadata", route: "/api/document-store/elasticsearch/index-metadata" },
|
||||
{ name: "elasticsearchDeleteAllDocuments", command: "elasticsearch_delete_all_documents", route: "/api/document-store/elasticsearch/documents/delete-all" },
|
||||
] as const;
|
||||
|
||||
describe("Elasticsearch index action dual transport contract", () => {
|
||||
it.each(operations)("registers $name end-to-end", ({ name, command, route }) => {
|
||||
expect(functionBody(tauri, name).match(/invoke\("([^"]+)"/)?.[1], `${name} invoke command`).toBe(command);
|
||||
expect(functionBody(http, name).match(/post\("([^"]+)"/)?.[1], `${name} HTTP route`).toBe(route);
|
||||
expect(api).toContain(`${name} = forward("${name}")`);
|
||||
expect(tauriRegistry).toContain(`commands::document_cmd::${command},`);
|
||||
expect(webRegistry).toContain(`"${route.replace(/^\/api/, "")}",`);
|
||||
});
|
||||
|
||||
it("sends the metadata kind literals the Rust enum deserializes", () => {
|
||||
const body = functionBody(tauri, "elasticsearchGetIndexMetadata");
|
||||
expect(body).toContain("kind,");
|
||||
// Mirrors ElasticsearchIndexMetadataKind's camelCase variant renaming.
|
||||
expect(tauri).toContain('export type ElasticsearchIndexMetadataKind = "mapping" | "settings" | "stats"');
|
||||
expect(coreOps).toContain("pub enum ElasticsearchIndexMetadataKind");
|
||||
});
|
||||
|
||||
it("keeps the clear-index result fields the partial-clear check reads", () => {
|
||||
for (const field of ["total", "deleted", "versionConflicts", "timedOut", "failures"]) {
|
||||
expect(tauri, `ElasticsearchDeleteByQueryResult.${field}`).toContain(` ${field}:`);
|
||||
}
|
||||
});
|
||||
|
||||
it("guards the destructive clear behind a backend write check", () => {
|
||||
const commands = readFileSync(new URL("../../../../../../src-tauri/src/commands/document_cmd.rs", import.meta.url), "utf8");
|
||||
const routes = readFileSync(new URL("../../../../../../crates/dbx-web/src/routes/document_store.rs", import.meta.url), "utf8");
|
||||
const tauriCommand = commands.slice(commands.indexOf("pub async fn elasticsearch_delete_all_documents("));
|
||||
const webRoute = routes.slice(routes.indexOf("pub async fn elasticsearch_delete_all_documents("));
|
||||
expect(tauriCommand.slice(0, tauriCommand.indexOf("\n}"))).toContain("ensure_connection_writable");
|
||||
expect(webRoute.slice(0, webRoute.indexOf("\n}"))).toContain("ensure_writable");
|
||||
});
|
||||
});
|
||||
@@ -790,6 +790,8 @@ export const mongoFindOneAndDelete = forward("mongoFindOneAndDelete");
|
||||
|
||||
// Elasticsearch
|
||||
export const elasticsearchListIndices = forward("elasticsearchListIndices");
|
||||
export const elasticsearchGetIndexMetadata = forward("elasticsearchGetIndexMetadata");
|
||||
export const elasticsearchDeleteAllDocuments = forward("elasticsearchDeleteAllDocuments");
|
||||
export const vectorListCollections = forward("vectorListCollections");
|
||||
|
||||
// History
|
||||
|
||||
@@ -147,6 +147,8 @@ import type {
|
||||
PromptTemplate,
|
||||
SshPromptResolution,
|
||||
MeilisearchIndexOverview,
|
||||
ElasticsearchIndexMetadataKind,
|
||||
ElasticsearchDeleteByQueryResult,
|
||||
} from "@/lib/backend/tauri";
|
||||
import type { QueryEditability } from "@/lib/sql/sqlAnalysis";
|
||||
import { isTerminalTransferProgress } from "@/lib/backend/transferProgress";
|
||||
@@ -3869,6 +3871,21 @@ export async function elasticsearchCountDocuments(connectionId: string, index: s
|
||||
});
|
||||
}
|
||||
|
||||
export async function elasticsearchGetIndexMetadata(connectionId: string, index: string, kind: ElasticsearchIndexMetadataKind): Promise<Record<string, any>> {
|
||||
return post("/api/document-store/elasticsearch/index-metadata", {
|
||||
connectionId,
|
||||
index,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
export async function elasticsearchDeleteAllDocuments(connectionId: string, index: string): Promise<ElasticsearchDeleteByQueryResult> {
|
||||
return post("/api/document-store/elasticsearch/documents/delete-all", {
|
||||
connectionId,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
export async function mongoCountDocuments(connectionId: string, database: string, collection: string, filter?: string, mode?: "accurate" | "legacy", executionId?: string): Promise<number> {
|
||||
return post("/api/mongo/count-documents", {
|
||||
connectionId,
|
||||
|
||||
@@ -3859,6 +3859,33 @@ export async function elasticsearchCountDocuments(connectionId: string, index: s
|
||||
});
|
||||
}
|
||||
|
||||
/** Read-only index metadata endpoints exposed on the Elasticsearch index context menu. */
|
||||
export type ElasticsearchIndexMetadataKind = "mapping" | "settings" | "stats";
|
||||
|
||||
/** Outcome of clearing an index: mapping and settings are kept, documents are not. */
|
||||
export interface ElasticsearchDeleteByQueryResult {
|
||||
total: number;
|
||||
deleted: number;
|
||||
versionConflicts: number;
|
||||
timedOut: boolean;
|
||||
failures: string[];
|
||||
}
|
||||
|
||||
export async function elasticsearchGetIndexMetadata(connectionId: string, index: string, kind: ElasticsearchIndexMetadataKind): Promise<Record<string, any>> {
|
||||
return invoke("elasticsearch_get_index_metadata", {
|
||||
connectionId,
|
||||
index,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
export async function elasticsearchDeleteAllDocuments(connectionId: string, index: string): Promise<ElasticsearchDeleteByQueryResult> {
|
||||
return invoke("elasticsearch_delete_all_documents", {
|
||||
connectionId,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
export async function mongoCountDocuments(connectionId: string, database: string, collection: string, filter?: string, mode?: "accurate" | "legacy", executionId?: string): Promise<number> {
|
||||
return invoke("mongo_count_documents", {
|
||||
connectionId,
|
||||
|
||||
Reference in new issue
Block a user