Q32: documents and knowledge appear in global search
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c8233d258
commit
cb464c3ee4
5 files changed
+58
No files matched your search
@@ -88,6 +88,8 @@ export class SearchService {
|
||||
...this.providerDocs(orgId),
|
||||
...this.socialDocs(orgId),
|
||||
...this.scheduledPostDocs(orgId),
|
||||
...this.documentDocs(orgId),
|
||||
...this.knowledgeDocs(orgId),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -156,6 +158,24 @@ export class SearchService {
|
||||
return rows.map((row) => doc("scheduled_post", row.id, createTitle(row.body_md), row.body_md, { type: "Social", state: row.state, account: row.social_account_id }));
|
||||
}
|
||||
|
||||
private documentDocs(orgId: string): SearchDoc[] {
|
||||
const rows = this.db.prepare("select id, title, doc_type, status, content from documents where org_id = ?").all(orgId) as Array<{ id: string; title: string; doc_type: string; status: string; content: string }>;
|
||||
return rows.map((row) => doc("document", row.id, row.title, row.content, { type: "Library", kind: row.doc_type, status: row.status }));
|
||||
}
|
||||
|
||||
private knowledgeDocs(orgId: string): SearchDoc[] {
|
||||
const rows = this.db.prepare("select id, topic, category, content, scope_type, superseded_by, valid_until from knowledge_entries where org_id = ?").all(orgId) as Array<{
|
||||
id: string; topic: string; category: string; content: string; scope_type: string; superseded_by: string | null; valid_until: string | null;
|
||||
}>;
|
||||
const now = new Date().toISOString();
|
||||
return rows.map((row) => doc("knowledge", row.id, row.topic, row.content, {
|
||||
type: "Library",
|
||||
category: row.category,
|
||||
scope: row.scope_type,
|
||||
state: row.superseded_by ? "superseded" : row.valid_until && row.valid_until <= now ? "expired" : "active",
|
||||
}));
|
||||
}
|
||||
|
||||
private canRead(orgId: string, employeeId: string, entityType: SearchEntityType, entityId: string): boolean {
|
||||
if (entityType === "chat_space") return this.canReadSpace(entityId, employeeId);
|
||||
if (entityType === "chat_message") return this.canReadMessage(orgId, entityId, employeeId);
|
||||
|
||||
@@ -27,6 +27,7 @@ export const zh: Record<string, string> = {
|
||||
"Advance": "推进",
|
||||
"Advanced": "高级",
|
||||
"Integrations": "集成密钥",
|
||||
"Document": "文档",
|
||||
"Applies to": "适用范围",
|
||||
"Category": "类别",
|
||||
"Content": "内容",
|
||||
|
||||
@@ -21,6 +21,8 @@ const LABELS: Record<SearchEntityType, string> = {
|
||||
provider: t("Provider"),
|
||||
social_account: t("Social"),
|
||||
scheduled_post: t("Post"),
|
||||
document: t("Document"),
|
||||
knowledge: t("Knowledge"),
|
||||
};
|
||||
|
||||
export function SearchDialog({
|
||||
@@ -142,6 +144,7 @@ function routeFor(entityType: SearchEntityType): RouteId {
|
||||
if (entityType === "file") return "files";
|
||||
if (entityType === "provider") return "settings";
|
||||
if (entityType === "social_account" || entityType === "scheduled_post") return "connectors";
|
||||
if (entityType === "document" || entityType === "knowledge") return "library";
|
||||
return "work";
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ export const SEARCH_ENTITY_TYPES = [
|
||||
"provider",
|
||||
"social_account",
|
||||
"scheduled_post",
|
||||
"document",
|
||||
"knowledge",
|
||||
] as const;
|
||||
|
||||
export type SearchEntityType = (typeof SEARCH_ENTITY_TYPES)[number];
|
||||
|
||||
@@ -6,6 +6,8 @@ import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
import { SearchService } from "../../src/main/services/search-service";
|
||||
import { DocumentService } from "../../src/main/services/document-service";
|
||||
import { KnowledgeService } from "../../src/main/services/knowledge-service";
|
||||
import { TASK_STATUS } from "../../src/shared/domain";
|
||||
import { createId } from "../../src/shared/ids";
|
||||
|
||||
@@ -94,4 +96,34 @@ describe("search service", () => {
|
||||
expect(search.search({ orgId, employeeId: outsiderId, query: "secret", entityTypes: ["chat_message"] })).toHaveLength(0);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("finds documents and knowledge by their bodies, with state facets", () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const search = new SearchService(connection.db);
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "Lib", employeeName: "Dana Lee", mission: "m", goal: "g" });
|
||||
});
|
||||
const employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
new DocumentService(connection.db).create({
|
||||
orgId, title: "Pricing memo", docType: "memo", content: "Enterprise tier costs twelve hundred dollars.",
|
||||
authorAgentId: null, reviewerAgentId: null, projectId: null, goalId: null, taskId: null,
|
||||
});
|
||||
const knowledge = new KnowledgeService(connection.db);
|
||||
const oldId = knowledge.create({
|
||||
orgId, topic: "Refund window", category: "policy", content: "Refunds within fourteen days.", importance: "normal",
|
||||
sourceType: "employee", sourceId: null, scopeType: "organization", scopeId: null, validUntil: null, supersedesId: null,
|
||||
});
|
||||
knowledge.create({
|
||||
orgId, topic: "Refund window", category: "policy", content: "Refunds within thirty days.", importance: "normal",
|
||||
sourceType: "employee", sourceId: null, scopeType: "organization", scopeId: null, validUntil: null, supersedesId: oldId,
|
||||
});
|
||||
|
||||
const docs = search.search({ orgId, employeeId, query: "twelve hundred" });
|
||||
expect(docs.map((r) => [r.entityType, r.title, r.facets.kind])).toEqual([["document", "Pricing memo", "memo"]]);
|
||||
const entries = search.search({ orgId, employeeId, query: "refunds", entityTypes: ["knowledge"] });
|
||||
expect(entries.map((r) => r.facets.state).sort()).toEqual(["active", "superseded"]);
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user