Skip to content

Commit

Permalink
core: add support for sorting search results
Browse files Browse the repository at this point in the history
search results are sorted by date created for now until
we add proper support for sorting search results
in the ui
  • Loading branch information
thecodrr committed Dec 23, 2024
1 parent 3092696 commit 8c70931
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 40 deletions.
97 changes: 74 additions & 23 deletions packages/core/src/api/lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.

import { match } from "fuzzyjs";
import Database from "./index.js";
import { Item, Note, TrashItem } from "../types.js";
import { Item, Note, SortOptions, TrashItem } from "../types.js";
import { DatabaseSchema, RawDatabaseSchema } from "../database/index.js";
import { AnyColumnWithTable, Kysely, sql } from "@streetwriters/kysely";
import { FilteredSelector } from "../database/sql-collection.js";
import { VirtualizedGrouping } from "../utils/virtualized-grouping.js";
import { logger } from "../logger.js";
import { rebuildSearchIndex } from "../database/fts.js";
import { transformQuery } from "../utils/query-transformer.js";
import { getSortSelectors } from "../utils/grouping.js";

type SearchResults<T> = {
sorted: (limit?: number) => Promise<VirtualizedGrouping<T>>;
Expand All @@ -43,7 +44,7 @@ export default class Lookup {
constructor(private readonly db: Database) {}

notes(query: string, notes?: FilteredSelector<Note>): SearchResults<Note> {
return this.toSearchResults(async (limit) => {
return this.toSearchResults(async (limit, sortOptions) => {
const db = this.db.sql() as unknown as Kysely<RawDatabaseSchema>;
const excludedIds = this.db.trash.cache.notes;

Expand Down Expand Up @@ -71,13 +72,16 @@ export default class Lookup {
)
.where("data", "match", query)
.select(["noteId as id", "rank"])
.$castTo<{ id: string; rank: number }>()
.$castTo<{
id: string;
rank: number;
}>()
)
.as("results")
)
.select(["results.id"])
.groupBy("results.id")
.orderBy(sql`SUM(results.rank)`, "desc")
.orderBy(sql`SUM(results.rank)`, sortOptions?.sortDirection || "desc")
.$if(!!limit, (eb) => eb.limit(limit!))

// filter out ids that have no note against them
Expand Down Expand Up @@ -119,9 +123,17 @@ export default class Lookup {
}

trash(query: string): SearchResults<TrashItem> {
const sortOptions: SortOptions = {
sortBy: "dateDeleted",
sortDirection: "desc"
};
return {
sorted: async (limit?: number) => {
const { ids, items } = await this.filterTrash(query, limit);
const { ids, items } = await this.filterTrash(
query,
limit,
sortOptions
);
return new VirtualizedGrouping<TrashItem>(
ids.length,
this.db.options.batchSize,
Expand All @@ -135,7 +147,7 @@ export default class Lookup {
);
},
items: async (limit?: number) => {
const { items } = await this.filterTrash(query, limit);
const { items } = await this.filterTrash(query, limit, sortOptions);
return items;
},
ids: () => this.filterTrash(query).then(({ ids }) => ids)
Expand All @@ -157,7 +169,8 @@ export default class Lookup {
fields: FuzzySearchField<T>[]
) {
return this.toSearchResults(
(limit) => this.filter(selector, query, fields, limit),
(limit, sortOptions) =>
this.filter(selector, query, fields, limit, sortOptions),
selector
);
}
Expand All @@ -166,7 +179,8 @@ export default class Lookup {
selector: FilteredSelector<T>,
query: string,
fields: FuzzySearchField<T>[],
limit?: number
limit?: number,
sortOptions?: SortOptions
) {
const results: Map<string, number> = new Map();
const columns = fields.map((f) => f.column);
Expand All @@ -183,24 +197,46 @@ export default class Lookup {
}
selector.fields([]);

return Array.from(results.entries())
.sort((a, b) => a[1] - b[1])
.map((a) => a[0]);
const sorted = Array.from(results.entries());

if (!sortOptions)
// || sortOptions.sortBy === "relevance")
sorted.sort(
// sortOptions?.sortDirection === "desc"
// ? (a, b) => a[1] - b[1]
// :
(a, b) => b[1] - a[1]
);

return sorted.map((a) => a[0]);
}

private toSearchResults<T extends Item>(
ids: (limit?: number) => Promise<string[]>,
ids: (limit?: number, sortOptions?: SortOptions) => Promise<string[]>,
selector: FilteredSelector<T>
): SearchResults<T> {
const sortOptions: SortOptions = {
sortBy: "dateCreated",
sortDirection: "desc"
};
return {
sorted: async (limit?: number) =>
this.toVirtualizedGrouping(await ids(limit), selector),
items: async (limit?: number) => this.toItems(await ids(limit), selector),
this.toVirtualizedGrouping(
await ids(limit, sortOptions),
selector,
sortOptions
),
items: async (limit?: number) =>
this.toItems(await ids(limit, sortOptions), selector, sortOptions),
ids
};
}

private async filterTrash(query: string, limit?: number) {
private async filterTrash(
query: string,
limit?: number,
sortOptions?: SortOptions
) {
const items = await this.db.trash.all();

const results: Map<string, { rank: number; item: TrashItem }> = new Map();
Expand All @@ -212,10 +248,20 @@ export default class Lookup {
results.set(item.id, { rank: result.score, item });
}
}
const sorted = Array.from(results.entries());

const sorted = Array.from(results.entries()).sort(
(a, b) => a[1].rank - b[1].rank
);
if (!sortOptions)
// || sortOptions.sortBy === "relevance")
sorted.sort(
// sortOptions?.sortDirection === "desc"
// ? (a, b) => a[1].rank - b[1].rank
// :
(a, b) => b[1].rank - a[1].rank
);
else {
const selector = getSortSelectors(sortOptions)[sortOptions.sortDirection];
sorted.sort((a, b) => selector(a[1].item, b[1].item));
}
return {
ids: sorted.map((a) => a[0]),
items: sorted.map((a) => a[1].item)
Expand All @@ -224,28 +270,33 @@ export default class Lookup {

private toVirtualizedGrouping<T extends Item>(
ids: string[],
selector: FilteredSelector<T>
selector: FilteredSelector<T>,
sortOptions?: SortOptions
) {
// if (sortOptions?.sortBy === "relevance") sortOptions = undefined;
return new VirtualizedGrouping<T>(
ids.length,
this.db.options.batchSize,
() => Promise.resolve(ids),
async (start, end) => {
const items = await selector.records(ids);
const items = await selector.items(ids.slice(start, end), sortOptions);
return {
ids: ids.slice(start, end),
items: Object.values(items).slice(start, end)
items
};
}
// (items) => groupArray(items, () => `${items.length} results`)
);
}

private toItems<T extends Item>(
ids: string[],
selector: FilteredSelector<T>
selector: FilteredSelector<T>,
sortOptions?: SortOptions
) {
if (!ids.length) return [];
return selector.items(ids);
// if (sortOptions?.sortBy === "relevance") sortOptions = undefined;
return selector.items(ids, sortOptions);
}

async rebuild() {
Expand Down
28 changes: 14 additions & 14 deletions packages/core/src/database/sql-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,9 @@ export class FilteredSelector<T extends Item> {
return (
await this.filter
.$if(!!sortOptions, (eb) =>
eb.$call(this.buildSortExpression(sortOptions!))
eb.$call(
this.buildSortExpression({ ...sortOptions!, groupBy: "none" })
)
)
.select("id")
.execute()
Expand All @@ -414,7 +416,7 @@ export class FilteredSelector<T extends Item> {
return (await this.filter
.$if(!!ids && ids.length > 0, (eb) => eb.where("id", "in", ids!))
.$if(!!sortOptions, (eb) =>
eb.$call(this.buildSortExpression(sortOptions!))
eb.$call(this.buildSortExpression({ ...sortOptions!, groupBy: "none" }))
)
.$if(this._fields.length === 0, (eb) => eb.selectAll())
.$if(this._fields.length > 0, (eb) => eb.select(this._fields))
Expand Down Expand Up @@ -528,7 +530,9 @@ export class FilteredSelector<T extends Item> {
if (options.groupBy === "abc") fields.push("title");
else if (options.sortBy === "title" && options.groupBy !== "none")
fields.push("dateCreated");
else if (options.sortBy !== "dueDate") fields.push(options.sortBy);
else if (options.sortBy !== "dueDate")
// && options.sortBy !== "relevance")
fields.push(options.sortBy);

return Array.from(
groupArray(
Expand All @@ -550,7 +554,7 @@ export class FilteredSelector<T extends Item> {
() => this.ids(options),
async (start, end) => {
const items = (await this.filter
.$call(this.buildSortExpression(options))
.$call(this.buildSortExpression({ ...options, groupBy: "none" }))
.offset(start)
.limit(end - start)
.selectAll()
Expand Down Expand Up @@ -596,18 +600,13 @@ export class FilteredSelector<T extends Item> {
}
}

private buildSortExpression(
options: GroupOptions | SortOptions,
hasDueDate?: boolean
) {
private buildSortExpression(options: GroupOptions, hasDueDate?: boolean) {
sanitizeSortOptions(this.type, options);

const sortBy: Set<SortOptions["sortBy"]> = new Set();
if (isGroupOptions(options)) {
if (options.groupBy === "abc") sortBy.add("title");
else if (options.sortBy === "title" && options.groupBy !== "none")
sortBy.add("dateCreated");
}
if (options.groupBy === "abc") sortBy.add("title");
else if (options.sortBy === "title" && options.groupBy !== "none")
sortBy.add("dateCreated");
sortBy.add(options.sortBy);

return <T>(
Expand Down Expand Up @@ -644,7 +643,8 @@ export class FilteredSelector<T extends Item> {
(qb) => qb.parens(createUpcomingReminderTimeQuery()),
options.sortDirection
);
} else qb = qb.orderBy(item, options.sortDirection);
} // if (item !== "relevance")
else qb = qb.orderBy(item, options.sortDirection);
continue;
}

Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/utils/grouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { isReminderActive } from "../collections/reminders.js";
import { GroupHeader, GroupOptions, ItemType, Reminder } from "../types.js";
import {
GroupHeader,
GroupOptions,
ItemType,
Reminder,
SortOptions
} from "../types.js";
import { getWeekGroupFromTimestamp, MONTHS_FULL } from "../utils/date.js";

type PartialGroupableItem = {
Expand All @@ -33,7 +39,7 @@ type PartialGroupableItem = {
export type GroupKeySelectorFunction<T> = (item: T) => string;

export const getSortValue = (
options: GroupOptions | undefined,
options: SortOptions | undefined,
item: PartialGroupableItem
) => {
if (
Expand All @@ -53,7 +59,7 @@ export const getSortValue = (
};

export function getSortSelectors<T extends PartialGroupableItem>(
options: GroupOptions
options: SortOptions
) {
if (options.sortBy === "title")
return {
Expand Down

0 comments on commit 8c70931

Please sign in to comment.