From 551f569896c0a59d29dd88bf93f8ee75f09df0ce Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 27 Dec 2023 16:56:27 -0500 Subject: [PATCH] feat: Allow filtering searches by 'source' fix: Do not show API searches in recent list in app --- app/components/SearchActions.ts | 4 +- app/models/SearchQuery.ts | 10 ++++- .../Search/components/RecentSearches.tsx | 4 +- app/stores/SearchesStore.ts | 4 +- server/presenters/searchQuery.ts | 1 + server/routes/api/searches/schema.ts | 10 +++++ server/routes/api/searches/searches.test.ts | 13 +++++++ server/routes/api/searches/searches.ts | 39 ++++++++++++------- 8 files changed, 65 insertions(+), 20 deletions(-) diff --git a/app/components/SearchActions.ts b/app/components/SearchActions.ts index 62577b55a..81f1f8dd9 100644 --- a/app/components/SearchActions.ts +++ b/app/components/SearchActions.ts @@ -11,7 +11,9 @@ export default function SearchActions() { React.useEffect(() => { if (!searches.isLoaded) { - void searches.fetchPage({}); + void searches.fetchPage({ + source: "app", + }); } }, [searches]); diff --git a/app/models/SearchQuery.ts b/app/models/SearchQuery.ts index 35d37c9a9..558681926 100644 --- a/app/models/SearchQuery.ts +++ b/app/models/SearchQuery.ts @@ -4,10 +4,16 @@ import Model from "./base/Model"; class SearchQuery extends Model { static modelName = "Search"; - id: string; - + /** + * The query string, automatically truncated to 255 characters. + */ query: string; + /** + * Where the query originated. + */ + source: "api" | "app" | "slack"; + delete = async () => { this.isSaving = true; diff --git a/app/scenes/Search/components/RecentSearches.tsx b/app/scenes/Search/components/RecentSearches.tsx index 67f09993c..5f725f2b8 100644 --- a/app/scenes/Search/components/RecentSearches.tsx +++ b/app/scenes/Search/components/RecentSearches.tsx @@ -28,7 +28,9 @@ function RecentSearches( const [isPreloaded] = React.useState(searches.recent.length > 0); React.useEffect(() => { - void searches.fetchPage({}); + void searches.fetchPage({ + source: "app", + }); }, [searches]); const content = searches.recent.length ? ( diff --git a/app/stores/SearchesStore.ts b/app/stores/SearchesStore.ts index e48bf7edf..b18bc6bfb 100644 --- a/app/stores/SearchesStore.ts +++ b/app/stores/SearchesStore.ts @@ -13,6 +13,8 @@ export default class SearchesStore extends Store { @computed get recent(): SearchQuery[] { - return uniqBy(this.orderedData, "query").slice(0, 8); + return uniqBy(this.orderedData, "query") + .filter((search) => search.source === "app") + .slice(0, 8); } } diff --git a/server/presenters/searchQuery.ts b/server/presenters/searchQuery.ts index 3a7d49cb0..efd78cb83 100644 --- a/server/presenters/searchQuery.ts +++ b/server/presenters/searchQuery.ts @@ -4,6 +4,7 @@ export default function presentSearchQuery(searchQuery: SearchQuery) { return { id: searchQuery.id, query: searchQuery.query, + source: searchQuery.source, createdAt: searchQuery.createdAt, answer: searchQuery.answer, score: searchQuery.score, diff --git a/server/routes/api/searches/schema.ts b/server/routes/api/searches/schema.ts index fa5deaa16..8349bd71a 100644 --- a/server/routes/api/searches/schema.ts +++ b/server/routes/api/searches/schema.ts @@ -21,3 +21,13 @@ export const SearchesUpdateSchema = BaseSchema.extend({ }); export type SearchesUpdateReq = z.infer; + +export const SearchesListSchema = BaseSchema.extend({ + body: z + .object({ + source: z.string().optional(), + }) + .optional(), +}); + +export type SearchesListReq = z.infer; diff --git a/server/routes/api/searches/searches.test.ts b/server/routes/api/searches/searches.test.ts index b2abdd4ef..dc0c3f157 100644 --- a/server/routes/api/searches/searches.test.ts +++ b/server/routes/api/searches/searches.test.ts @@ -24,6 +24,7 @@ describe("#searches.list", () => { userId: user.id, teamId: user.teamId, query: "bar", + source: "api", }), ]); }); @@ -42,6 +43,18 @@ describe("#searches.list", () => { expect(queries).toContain("foo"); expect(queries).toContain("bar"); }); + + it("should allow filtering by source", async () => { + const res = await server.post("/api/searches.list", { + body: { + token: user.getJwtToken(), + source: "api", + }, + }); + const body = await res.json(); + expect(res.status).toEqual(200); + expect(body.data).toHaveLength(1); + }); }); describe("#searches.update", () => { diff --git a/server/routes/api/searches/searches.ts b/server/routes/api/searches/searches.ts index f7aa7d8a5..1e3e42f79 100644 --- a/server/routes/api/searches/searches.ts +++ b/server/routes/api/searches/searches.ts @@ -10,23 +10,32 @@ import * as T from "./schema"; const router = new Router(); -router.post("searches.list", auth(), pagination(), async (ctx: APIContext) => { - const { user } = ctx.state.auth; +router.post( + "searches.list", + auth(), + validate(T.SearchesListSchema), + pagination(), + async (ctx: APIContext) => { + const { user } = ctx.state.auth; + const source = ctx.input.body?.source; - const searches = await SearchQuery.findAll({ - where: { - userId: user.id, - }, - order: [["createdAt", "DESC"]], - offset: ctx.state.pagination.offset, - limit: ctx.state.pagination.limit, - }); + const searches = await SearchQuery.findAll({ + where: { + ...(source ? { source } : {}), + teamId: user.teamId, + userId: user.id, + }, + order: [["createdAt", "DESC"]], + offset: ctx.state.pagination.offset, + limit: ctx.state.pagination.limit, + }); - ctx.body = { - pagination: ctx.state.pagination, - data: searches.map(presentSearchQuery), - }; -}); + ctx.body = { + pagination: ctx.state.pagination, + data: searches.map(presentSearchQuery), + }; + } +); router.post( "searches.update",