Skip to content

Commit

Permalink
feat: add ability to search in docs & select search
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Hivert <[email protected]>
  • Loading branch information
ghivert committed May 18, 2024
1 parent 71aa71a commit 68ec71d
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 29 deletions.
34 changes: 31 additions & 3 deletions apps/backend/src/backend/postgres/queries.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ pub fn type_search_to_json(item) {
])
}

pub fn search(db: pgo.Connection, q: String) {
let query = pgo.text("'" <> q <> "'")
pub fn signature_search(db: pgo.Connection, q: String) {
let query = pgo.text(q)
"SELECT DISTINCT ON (package_rank, type_name, signature_kind, module_name)
s.name type_name,
s.documentation,
Expand All @@ -539,7 +539,35 @@ pub fn search(db: pgo.Connection, q: String) {
ON m.package_release_id = r.id
JOIN package p
ON p.id = r.package_id
WHERE to_tsvector('english', s.signature_) @@ to_tsquery($1)
WHERE to_tsvector('english', s.signature_) @@ websearch_to_tsquery($1)
ORDER BY package_rank DESC, type_name, signature_kind, module_name, ordering DESC
LIMIT 100"
|> pgo.execute(db, [query], decode_type_search)
|> result.map_error(error.DatabaseError)
|> result.map(fn(r) { r.rows })
}

pub fn documentation_search(db: pgo.Connection, q: String) {
let query = pgo.text(q)
"SELECT DISTINCT ON (package_rank, type_name, signature_kind, module_name)
s.name type_name,
s.documentation,
s.kind signature_kind,
s.metadata,
s.json_signature,
m.name module_name,
p.name,
r.version,
p.rank package_rank,
string_to_array(regexp_replace(r.version, '([0-9]+).([0-9]+).([0-9]+).*', '\\1.\\2.\\3'), '.')::int[] AS ordering
FROM package_type_fun_signature s
JOIN package_module m
ON m.id = s.package_module_id
JOIN package_release r
ON m.package_release_id = r.id
JOIN package p
ON p.id = r.package_id
WHERE to_tsvector('english', s.documentation) @@ websearch_to_tsquery($1)
ORDER BY package_rank DESC, type_name, signature_kind, module_name, ordering DESC
LIMIT 100"
|> pgo.execute(db, [query], decode_type_search)
Expand Down
72 changes: 57 additions & 15 deletions apps/backend/src/backend/router.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import backend/error
import backend/postgres/queries
import backend/web
import cors_builder as cors
import gleam/bool
import gleam/http
import gleam/json
import gleam/list
import gleam/pair
import gleam/result
import gleam/string
import gleam/string_builder
import tasks/hex as syncing
import wisp.{type Request, type Response}
Expand All @@ -19,28 +22,67 @@ fn empty_json() {
|> wisp.json_response(200)
}

fn isolate_filters(query: String) -> #(String, List(String)) {
string.split(query, " ")
|> list.fold(#([], []), fn(acc, val) {
case val {
"in:signature" | "in:name" | "in:documentation" -> #(acc.0, [val, ..acc.1])
_ -> #([val, ..acc.0], acc.1)
}
})
|> pair.map_first(list.reverse)
|> pair.map_first(string.join(_, " "))
|> pair.map_second(fn(filters) {
let no_filters = list.is_empty(filters)
use <- bool.guard(when: no_filters, return: ["in:signature", "in:name"])
filters
})
}

fn search(query: String, ctx: Context) {
wisp.log_notice("Searching for " <> query)
let exact_matches =
queries.name_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
let matches =
queries.content_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
|> list.filter(fn(i) { !list.contains(exact_matches, i) })
json.object([
#("exact-matches", json.array(exact_matches, queries.type_search_to_json)),
#("matches", json.array(matches, queries.type_search_to_json)),
#("searches", {
queries.search(ctx.db, query)
let #(query, filters) = isolate_filters(query)
let exact_matches = case list.contains(filters, "in:name") {
False -> []
True ->
queries.name_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
}
let matches = case list.contains(filters, "in:signature") {
False -> []
True ->
queries.content_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
|> list.filter(fn(i) { !list.contains(exact_matches, i) })
}
let signature_searches = case list.contains(filters, "in:signature") {
False -> []
True ->
queries.signature_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
|> list.filter(fn(i) {
!list.contains(list.append(exact_matches, matches), i)
})
|> json.array(queries.type_search_to_json)
}
let documentation_searches = case list.contains(filters, "in:documentation") {
False -> []
True ->
queries.documentation_search(ctx.db, query)
|> result.map_error(error.debug_log)
|> result.unwrap([])
|> list.filter(fn(i) {
!list.contains(list.append(exact_matches, matches), i)
})
}
json.object([
#("exact-matches", json.array(exact_matches, queries.type_search_to_json)),
#("matches", json.array(matches, queries.type_search_to_json)),
#("searches", json.array(signature_searches, queries.type_search_to_json)),
#("docs-searches", {
json.array(documentation_searches, queries.type_search_to_json)
}),
])
}
Expand Down
13 changes: 7 additions & 6 deletions apps/frontend/src/data/model.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn init() {
let search_results = search_result.Start
let index = compute_index(search_results)
Model(
input: "in:name in:signature ",
input: "",
search_results: search_results,
index: index,
loading: False,
Expand All @@ -50,8 +50,8 @@ pub fn update_search_results(model: Model, search_results: SearchResults) {
let index = compute_index(search_results)
let view_cache = case search_results {
search_result.Start | search_result.InternalServerError -> element.none()
search_result.SearchResults(e, m, s) ->
cache.cache_search_results(index, e, m, s)
search_result.SearchResults(e, m, s, d) ->
cache.cache_search_results(index, e, m, s, d)
}
Model(
..model,
Expand All @@ -63,8 +63,8 @@ pub fn update_search_results(model: Model, search_results: SearchResults) {

pub fn reset(_model: Model) {
Model(
search_results: search_result.SearchResults([], [], []),
input: "in:name in:signature ",
search_results: search_result.SearchResults([], [], [], []),
input: "",
index: [],
loading: False,
view_cache: element.none(),
Expand All @@ -75,11 +75,12 @@ pub fn reset(_model: Model) {
fn compute_index(search_results: SearchResults) -> Index {
case search_results {
search_result.Start | search_result.InternalServerError -> []
search_result.SearchResults(exact, others, searches) -> {
search_result.SearchResults(exact, others, searches, docs) -> {
[]
|> insert_module_names(exact)
|> insert_module_names(others)
|> insert_module_names(searches)
|> insert_module_names(docs)
|> list.map(fn(i) { pair.map_second(i, list.reverse) })
}
}
Expand Down
6 changes: 4 additions & 2 deletions apps/frontend/src/data/search_result.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ pub type SearchResults {
SearchResults(
exact_matches: List(SearchResult),
matches: List(SearchResult),
searches: List(SearchResult),
signature_searches: List(SearchResult),
docs_searches: List(SearchResult),
)
}

Expand All @@ -46,11 +47,12 @@ pub fn decode_search_results(dyn) {
dynamic.decode1(fn(_) { InternalServerError }, {
dynamic.field("error", dynamic.string)
}),
dynamic.decode3(
dynamic.decode4(
SearchResults,
dynamic.field("exact-matches", dynamic.list(decode_search_result)),
dynamic.field("matches", dynamic.list(decode_search_result)),
dynamic.field("searches", dynamic.list(decode_search_result)),
dynamic.field("docs-searches", dynamic.list(decode_search_result)),
),
])(dyn)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/frontend.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ fn handle_search_results(
fn handle_route_change(model: Model, route: router.Route) {
let model = model.update_route(model, route)
update.none(case route {
router.Home -> model.update_input(model, "in:name in:signature ")
router.Home -> model.update_input(model, "")
router.Search(q) -> model.update_input(model, q)
})
}
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/frontend/strings.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub const partial_match = "Partly matched from the signature of functions, const

pub const searches_match = "Matched from a document search in functions, constants or types."

pub const docs_match = "Matched from a documentation search in functions, constants or types."

pub const retry_query = "Retry with a different query. You can match functions, types or constants names, as well as functions types directly!"

pub const internal_server_error = "Internal server error. The error should be fixed soon. Please, retry later."
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/frontend/view/body/body.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ pub fn body(model: Model) {
title: "Internal server error",
content: frontend_strings.internal_server_error,
)
search_result.SearchResults([], [], []) ->
search_result.SearchResults([], [], [], []) ->
empty_state(
image: images.shadow_lucy,
title: "No match found!",
content: frontend_strings.retry_query,
)
search_result.SearchResults(_, _, _) -> model.view_cache
search_result.SearchResults(_, _, _, _) -> model.view_cache
}
},
])
Expand Down
7 changes: 7 additions & 0 deletions apps/frontend/src/frontend/view/body/cache.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub fn cache_search_results(
exact: List(search_result.SearchResult),
others: List(search_result.SearchResult),
searches: List(search_result.SearchResult),
docs_searches: List(search_result.SearchResult),
) {
s.search_results_wrapper([], [
sidebar(index),
Expand All @@ -121,6 +122,12 @@ pub fn cache_search_results(
view_search_results(others),
match_title(searches, "Searches matches", frontend_strings.searches_match),
view_search_results(searches),
match_title(
docs_searches,
"Documentation matches",
frontend_strings.docs_match,
),
view_search_results(docs_searches),
]),
])
}

0 comments on commit 68ec71d

Please sign in to comment.