Skip to content

Commit

Permalink
expose more comprehensive set of search params to the assistant
Browse files Browse the repository at this point in the history
  • Loading branch information
nl0 committed Jul 18, 2024
1 parent 19bb829 commit 13c937f
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 21 deletions.
1 change: 1 addition & 0 deletions catalog/app/components/Assistant/Model/Conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ Your primary purpose is assisting users of Quilt Data products.
Persona: conservative and creative scientist.
`

// TODO: mention the client company?
const TASK_CONTEXT = `

Check warning on line 291 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L291

Added line #L291 was not covered by tests
<task-context>
You act as a chatbot deployed on the Quilt Catalog web app.
Expand Down
117 changes: 97 additions & 20 deletions catalog/app/components/Assistant/Model/GlobalTools/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,64 @@ import * as S from '@effect/schema/Schema'

import * as SearchModel from 'containers/Search/model'
import * as Model from 'model'
import * as Log from 'utils/Logging'

Check warning on line 7 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L5-L7

Added lines #L5 - L7 were not covered by tests

import * as Content from '../Content'
import * as Tool from '../Tool'

Check warning on line 10 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L9-L10

Added lines #L9 - L10 were not covered by tests

// TODO: more comprehensive params
const SearchParamsSchema = S.Struct({
resultType: S.Enums(SearchModel.ResultType).annotations({
title: 'result type',
description: 'whether to search for objects or packages',
const MODULE = 'GlobalTools/search'

Check warning on line 12 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L12

Added line #L12 was not covered by tests

const PackageFilterSchema = S.Struct({

Check warning on line 14 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L14

Added line #L14 was not covered by tests
modified: S.optional(SearchModel.PredicatesJSON.Datetime),
size: S.optional(SearchModel.PredicatesJSON.Number),
name: S.optional(SearchModel.PredicatesJSON.KeywordWildcard),
hash: S.optional(SearchModel.PredicatesJSON.KeywordWildcard),
entries: S.optional(SearchModel.PredicatesJSON.Number),
comment: S.optional(SearchModel.PredicatesJSON.Text),
workflow: S.optional(SearchModel.PredicatesJSON.KeywordEnum),
})

const PackageSearchParamsSchema = S.Struct({

Check warning on line 24 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L24

Added line #L24 was not covered by tests
resultType: S.Literal(SearchModel.ResultType.QuiltPackage).annotations({
title: 'result type: Quilt Package',
}),
filter: S.optional(PackageFilterSchema).annotations({
title: 'result filters',
}),
userMetaFilters: S.optional(SearchModel.UserMetaFiltersSchema).annotations({
title: 'user metadata filters',
description: 'a map of user metadata field paths to predicate values',
}),
}).annotations({
title: 'package-specific search parameters',
})

const ObjectFilterSchema = S.Struct({

Check warning on line 39 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L39

Added line #L39 was not covered by tests
modified: S.optional(SearchModel.PredicatesJSON.Datetime),
size: S.optional(SearchModel.PredicatesJSON.Number),
ext: S.optional(SearchModel.PredicatesJSON.KeywordEnum).annotations({
title: 'file extensions (with a leading dot)',
}),
key: S.optional(SearchModel.PredicatesJSON.KeywordWildcard),
content: S.optional(SearchModel.PredicatesJSON.Text),
deleted: S.optional(SearchModel.PredicatesJSON.Boolean),
})

const ObjectSearchParamsSchema = S.Struct({

Check warning on line 50 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L50

Added line #L50 was not covered by tests
resultType: S.Literal(SearchModel.ResultType.S3Object).annotations({
title: 'result type: S3 Object',
}),
filter: S.optional(ObjectFilterSchema).annotations({
title: 'result filters',
}),
}).annotations({
title: 'object-specific search parameters',
})

const SearchParamsSchema = S.Struct({

Check warning on line 61 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L61

Added line #L61 was not covered by tests
searchString: S.optional(S.String).annotations({
title: 'search string',
description: 'string to search for',
description: 'string to search for, ElasticSearch syntax supported',
}),
buckets: S.Array(S.String).annotations({
title: 'search buckets',
Expand All @@ -26,31 +71,63 @@ const SearchParamsSchema = S.Struct({
title: 'search order',
description: 'order of search results',
}),
params: S.Union(PackageSearchParamsSchema, ObjectSearchParamsSchema).annotations({
title: 'result type-specific parameters',
}),
}).annotations({
title: 'search parameters',
description: 'Start a search session',
})

type SearchParams = S.Schema.Type<typeof SearchParamsSchema>

function searchUrlStateFromSearchParams({
params,
searchString,
...rest
}: SearchParams): SearchModel.SearchUrlState {

Check warning on line 88 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L85-L88

Added lines #L85 - L88 were not covered by tests
const base = { searchString: searchString ?? null, ...rest }
switch (params.resultType) {
case SearchModel.ResultType.S3Object:
return {

Check warning on line 92 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L91-L92

Added lines #L91 - L92 were not covered by tests
...base,
resultType: params.resultType,
filter: SearchModel.ObjectsSearchFilterIO.fromJSON(params.filter),
}
case SearchModel.ResultType.QuiltPackage:
return {

Check warning on line 98 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L97-L98

Added lines #L97 - L98 were not covered by tests
...base,
resultType: params.resultType,
filter: SearchModel.PackagesSearchFilterIO.fromJSON({}),
userMetaFilters: SearchModel.UserMetaFilters.fromJSON(params.userMetaFilters),
}
default:
return Eff.absurd<never>(params)

Check warning on line 105 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L104-L105

Added lines #L104 - L105 were not covered by tests
}
}

export function useStartSearch() {
const makeUrl = SearchModel.useMakeUrl()
const history = useHistory()
return Tool.useMakeTool(

Check warning on line 112 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L109-L112

Added lines #L109 - L112 were not covered by tests
SearchParamsSchema,
(params) =>
Eff.Effect.gen(function* () {
yield* Eff.Console.debug('tool: start search', params)

const defaultParams = SearchModel.parseSearchParams(`t=${params.resultType}`)
const url = makeUrl({ ...defaultParams, ...params } as SearchModel.SearchUrlState)
yield* Eff.Effect.sync(() => history.push(url))
return Eff.Option.some(
Tool.succeed(
Content.ToolResultContentBlock.Text({
text: 'Navigating to the search page and starting the search session. Use catalog_search_getResults tool to get the search results.',
}),
),
)
}),
Log.scoped({

Check warning on line 115 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L114-L115

Added lines #L114 - L115 were not covered by tests
name: `${MODULE}.startSearch`,
enter: [Log.br, 'params:', params],
})(
Eff.Effect.gen(function* () {
const url = makeUrl(searchUrlStateFromSearchParams(params))
yield* Eff.Effect.sync(() => history.push(url))
return Eff.Option.some(

Check warning on line 122 in catalog/app/components/Assistant/Model/GlobalTools/search.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/GlobalTools/search.ts#L119-L122

Added lines #L119 - L122 were not covered by tests
Tool.succeed(
Content.ToolResultContentBlock.Text({
text: 'Navigating to the search page and starting the search session. Use catalog_search_getResults tool to get the search results.',
}),
),
)
}),
),
[makeUrl, history],
)
}
101 changes: 100 additions & 1 deletion catalog/app/containers/Search/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as R from 'ramda'
import * as React from 'react'
import * as RR from 'react-router-dom'
import { useDebounce } from 'use-debounce'
import * as S from '@effect/schema/Schema'
import * as Sentry from '@sentry/react'

import * as Model from 'model'
Expand Down Expand Up @@ -99,6 +100,7 @@ interface PredicateIO<Tag extends string, State, GQLType> {
readonly _tag: Tag
initialState: Tagged<Tag, State>
fromString: (input: string) => Tagged<Tag, State>
fromJSON: (input: unknown) => Tagged<Tag, State>
toString: (state: Tagged<Tag, State>) => string | null
toGQL: (state: Tagged<Tag, State>) => GQLType | null
}
Expand All @@ -118,13 +120,15 @@ function Predicate<Tag extends string, State, GQLType>(input: {
tag: Tag
init: State
fromString: (input: string) => State
fromJSON: (input: unknown) => State
toString: (state: Tagged<Tag, State>) => string | null
toGQL: (state: Tagged<Tag, State>) => GQLType | null
}): PredicateIO<Tag, State, GQLType> {
return {
_tag: input.tag,
initialState: addTag(input.tag, input.init),
fromString: (s: string) => addTag(input.tag, input.fromString(s)),
fromJSON: (s: unknown) => addTag(input.tag, input.fromJSON(s)),

Check warning on line 131 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L131

Added line #L131 was not covered by tests
toString: input.toString,
toGQL: input.toGQL,
}
Expand All @@ -144,6 +148,10 @@ export const Predicates = {
lte: parseDate(json.lte),
}
},
fromJSON: (input: unknown) => ({

Check warning on line 151 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L151

Added line #L151 was not covered by tests
gte: parseDate((input as any)?.gte),
lte: parseDate((input as any)?.lte),
}),
toString: ({ _tag, ...state }) => JSON.stringify(state),
toGQL: ({ _tag, ...state }) =>
state.gte == null && state.lte === null
Expand All @@ -164,6 +172,10 @@ export const Predicates = {
lte: (json.lte as number) ?? null,
}
},
fromJSON: (input: unknown) => ({

Check warning on line 175 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L175

Added line #L175 was not covered by tests
gte: ((input as any)?.gte as number) ?? null,
lte: ((input as any)?.lte as number) ?? null,
}),
toString: ({ _tag, ...state }) => JSON.stringify(state),
toGQL: ({ _tag, ...state }) =>
state.gte == null && state.lte === null
Expand All @@ -175,6 +187,7 @@ export const Predicates = {
tag: 'Text',
init: { queryString: '' },
fromString: (input: string) => ({ queryString: input }),
fromJSON: (input: unknown) => ({ queryString: input as string }),

Check warning on line 190 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L190

Added line #L190 was not covered by tests
toString: ({ _tag, ...state }) => state.queryString.trim(),
toGQL: ({ _tag, ...state }) => {
const queryString = state.queryString.trim()
Expand All @@ -186,6 +199,7 @@ export const Predicates = {
tag: 'KeywordEnum',
init: { terms: [] as string[] },
fromString: (input: string) => ({ terms: JSON.parse(`[${input}]`) as string[] }),
fromJSON: (input: unknown) => ({ terms: input as string[] }),

Check warning on line 202 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L202

Added line #L202 was not covered by tests
toString: ({ terms }) => JSON.stringify(terms).slice(1, -1),
toGQL: ({ terms }) =>
terms.length
Expand All @@ -199,6 +213,7 @@ export const Predicates = {
wildcard: '' as string,
},
fromString: (wildcard: string) => ({ wildcard }),
fromJSON: (input: unknown) => ({ wildcard: input as string }),

Check warning on line 216 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L216

Added line #L216 was not covered by tests
toString: ({ wildcard }) => wildcard,
toGQL: ({ wildcard }) =>
wildcard
Expand All @@ -213,6 +228,10 @@ export const Predicates = {
const values = input.split(',')
return { true: values.includes('true'), false: values.includes('false') }
},
fromJSON: (input: unknown) => ({

Check warning on line 231 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L231

Added line #L231 was not covered by tests
true: (input as any)?.includes?.('true'),
false: (input as any)?.includes?.('false'),
}),
toString: (state) => {
const values = []
if (state.true) values.push('true')
Expand Down Expand Up @@ -259,6 +278,7 @@ type CombinedGQLType<PM extends PredicateMap> = {

interface FilterIO<PM extends PredicateMap> {
fromURLSearchParams: (params: URLSearchParams) => OrderedCombinedState<PM>
fromJSON: (params: Record<string, unknown> | undefined) => OrderedCombinedState<PM>
toURLSearchParams: (state: OrderedCombinedState<PM>) => [string, string][]
toGQL: (state: OrderedCombinedState<PM>) => CombinedGQLType<PM> | null
children: PM
Expand Down Expand Up @@ -303,6 +323,19 @@ function Filter<PM extends PredicateMap>(children: PM): FilterIO<PM> {
return state
}

function fromJSON(
params: Record<string, unknown> | undefined,

Check warning on line 327 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L327

Added line #L327 was not covered by tests
): OrderedCombinedState<PM> {
const state = initState()

Check warning on line 329 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L329

Added line #L329 was not covered by tests
Object.entries(params ?? {}).forEach(([k, v]) => {
const predicate = children[k]

Check warning on line 331 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L331

Added line #L331 was not covered by tests
if (!predicate) return
state.order.push(k as keyof PM)
state.predicates[k as keyof PM] = predicate.fromJSON(v)

Check warning on line 334 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L333-L334

Added lines #L333 - L334 were not covered by tests
})
return state

Check warning on line 336 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L336

Added line #L336 was not covered by tests
}

function toURLSearchParams(state: OrderedCombinedState<PM>): [string, string][] {
const params: [string, string][] = []
state.order.forEach((k) => {
Expand Down Expand Up @@ -373,6 +406,7 @@ function Filter<PM extends PredicateMap>(children: PM): FilterIO<PM> {

return {
fromURLSearchParams,
fromJSON,
toURLSearchParams,
toGQL,
children,
Expand Down Expand Up @@ -406,9 +440,62 @@ export const PackagesSearchFilterIO = Filter({
workflow: Predicates.KeywordEnum,
})

// XXX: identifiers for predicate types?
export const PredicatesJSON = {
Datetime: S.Struct({
gte: S.optional(S.NullOr(S.DateFromString)),
lte: S.optional(S.NullOr(S.DateFromString)),
}),
Number: S.Struct({
gte: S.optional(S.NullOr(S.Number)),
lte: S.optional(S.NullOr(S.Number)),
}),
Text: S.String,
KeywordEnum: S.Array(S.String),
KeywordWildcard: S.String,
Boolean: S.Array(S.Literal('true', 'false')),
}

// XXX: somehow map instead of enumerating manually?
export const PredicateWithState = S.Union(
S.Struct({
path: S.String,
type: S.Literal('Datetime'),
value: PredicatesJSON.Datetime,
}),
S.Struct({
path: S.String,
type: S.Literal('Number'),
value: PredicatesJSON.Number,
}),
S.Struct({
path: S.String,
type: S.Literal('Text'),
value: PredicatesJSON.Text,
}),
S.Struct({
path: S.String,
type: S.Literal('KeywordEnum'),
value: PredicatesJSON.KeywordEnum,
}),
S.Struct({
path: S.String,
type: S.Literal('KeywordWildcard'),
value: PredicatesJSON.KeywordWildcard,
}),
S.Struct({
path: S.String,
type: S.Literal('Boolean'),
value: PredicatesJSON.Boolean,
}),
)

// XXX: make it a record?
export const UserMetaFiltersSchema = S.Array(PredicateWithState)

type UserMetaFilterMap = Map<string, PredicateState<KnownPredicate>>

class UserMetaFilters {
export class UserMetaFilters {
filters: UserMetaFilterMap

static typeMap: Record<string, KnownPredicate> = {
Expand Down Expand Up @@ -455,6 +542,18 @@ class UserMetaFilters {
return new this(filters)
}

static fromJSON(
params: S.Schema.Type<typeof UserMetaFiltersSchema> | undefined,

Check warning on line 546 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L546

Added line #L546 was not covered by tests
): UserMetaFilters {
const filters: UserMetaFilterMap = new Map()

Check warning on line 548 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L548

Added line #L548 was not covered by tests
;(params ?? []).forEach(({ path, type, value }) => {
const predicate = UserMetaFilters.typeMap[type]

Check warning on line 550 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L550

Added line #L550 was not covered by tests
if (!predicate) return
filters.set(path, predicate.fromJSON(value))

Check warning on line 552 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L552

Added line #L552 was not covered by tests
})
return new this(filters)

Check warning on line 554 in catalog/app/containers/Search/model.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/containers/Search/model.ts#L554

Added line #L554 was not covered by tests
}

constructor(filters?: UserMetaFilterMap) {
this.filters = filters || new Map()
}
Expand Down

0 comments on commit 13c937f

Please sign in to comment.