[questions][feat] add content search (#478)

Co-authored-by: Jeff Sieu <jeffsy00@gmail.com>
pull/498/head
hpkoh 2 years ago committed by GitHub
parent e7431867c2
commit 397ea3f4aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,10 +9,14 @@ import SortOptionsSelect from './SortOptionsSelect';
export type QuestionSearchBarProps = SortOptionsSelectProps & {
onFilterOptionsToggle: () => void;
onQueryChange: (query: string) => void;
query: string;
};
export default function QuestionSearchBar({
onFilterOptionsToggle,
onQueryChange,
query,
...sortOptionsSelectProps
}: QuestionSearchBarProps) {
return (
@ -24,6 +28,10 @@ export default function QuestionSearchBar({
placeholder="Search by content"
startAddOn={MagnifyingGlassIcon}
startAddOnType="icon"
value={query}
onChange={(value) => {
onQueryChange(value);
}}
/>
</div>
<div className="flex items-end justify-end gap-4">

@ -218,7 +218,7 @@ export default function BaseQuestionCard({
</div>
<p
className={clsx(
'whitespace-pre-line',
'whitespace-pre-line font-semibold',
truncateContent && 'line-clamp-2 text-ellipsis',
)}>
{content}

@ -42,7 +42,9 @@ export default function CreateQuestionEncounterForm({
return (
<div className="flex items-center gap-2">
<p className="font-md text-md text-slate-600">I saw this question at</p>
<p className="font-md text-md text-slate-600">
I saw this question {step <= 1 ? 'at' : step === 2 ? 'for' : 'on'}
</p>
{step === 0 && (
<div>
<CompanyTypeahead

@ -37,6 +37,8 @@ import { SortOrder } from '~/types/questions.d';
export default function QuestionsBrowsePage() {
const router = useRouter();
const [query, setQuery] = useState('');
const [
selectedCompanySlugs,
setSelectedCompanySlugs,
@ -160,13 +162,14 @@ export default function QuestionsBrowsePage() {
const questionsInfiniteQuery = trpc.useInfiniteQuery(
[
'questions.questions.getQuestionsByFilter',
'questions.questions.getQuestionsByFilterAndContent',
{
// TODO: Enable filtering by countryIds and stateIds
cityIds: selectedLocations
.map(({ cityId }) => cityId)
.filter((id) => id !== undefined) as Array<string>,
companyIds: selectedCompanySlugs.map((slug) => slug.split('_')[0]),
content: query,
countryIds: [],
endDate: today,
limit: 10,
@ -475,8 +478,8 @@ export default function QuestionsBrowsePage() {
</Head>
<main className="flex flex-1 flex-col items-stretch">
<div className="flex h-full flex-1">
<section className="flex min-h-0 flex-1 flex-col items-center overflow-auto">
<div className="m-4 flex max-w-3xl flex-1 flex-col items-stretch justify-start gap-6">
<section className="min-h-0 flex-1 overflow-auto">
<div className="my-4 mx-auto flex max-w-3xl flex-col items-stretch justify-start gap-6">
<ContributeQuestionCard
onSubmit={(data) => {
const { cityId, countryId, stateId } = data.location;
@ -495,11 +498,15 @@ export default function QuestionsBrowsePage() {
<div className="flex flex-col items-stretch gap-4">
<div className="sticky top-0 border-b border-slate-300 bg-slate-50 py-4">
<QuestionSearchBar
query={query}
sortOrderValue={sortOrder}
sortTypeValue={sortType}
onFilterOptionsToggle={() => {
setFilterDrawerOpen(!filterDrawerOpen);
}}
onQueryChange={(newQuery) => {
setQuery(newQuery);
}}
onSortOrderChange={setSortOrder}
onSortTypeChange={setSortType}
/>

@ -236,7 +236,8 @@ export const questionsQuestionRouter = createRouter()
SELECT id FROM "QuestionsQuestion"
WHERE
to_tsvector("content") @@ to_tsquery('english', ${query})
ORDER BY ts_rank_cd(to_tsvector("content"), to_tsquery('english', ${query}), 4) DESC;
ORDER BY ts_rank_cd(to_tsvector("content"), to_tsquery('english', ${query}), 4) DESC
LIMIT 3;
`;
const relatedQuestionsIdArray = relatedQuestionsId.map(
@ -281,4 +282,183 @@ export const questionsQuestionRouter = createRouter()
return processedQuestionsData;
},
})
.query('getQuestionsByFilterAndContent', {
input: z.object({
cityIds: z.string().array(),
companyIds: z.string().array(),
content: z.string(),
countryIds: z.string().array(),
cursor: z.string().nullish(),
endDate: z.date().default(new Date()),
limit: z.number().min(1).default(50),
questionTypes: z.nativeEnum(QuestionsQuestionType).array(),
roles: z.string().array(),
sortOrder: z.nativeEnum(SortOrder),
sortType: z.nativeEnum(SortType),
startDate: z.date().optional(),
stateIds: z.string().array(),
}),
async resolve({ ctx, input }) {
const escapeChars = /[()|&:*!]/g;
const query = input.content
.replace(escapeChars, ' ')
.trim()
.split(/\s+/)
.join(' | ');
let relatedQuestionsId: Array<{ id: string }> = [];
if (input.content !== "") {
relatedQuestionsId = await ctx.prisma
.$queryRaw`
SELECT id FROM "QuestionsQuestion"
WHERE
to_tsvector("content") @@ to_tsquery('english', ${query})
ORDER BY ts_rank_cd(to_tsvector("content"), to_tsquery('english', ${query}), 4) DESC
LIMIT 3;
`;
}
const relatedQuestionsIdArray = relatedQuestionsId.map(
(current) => current.id,
);
const { cursor } = input;
const sortCondition =
input.sortType === SortType.TOP
? [
{
upvotes: input.sortOrder,
},
{
id: input.sortOrder,
},
]
: [
{
lastSeenAt: input.sortOrder,
},
{
id: input.sortOrder,
},
];
const questionsData = await ctx.prisma.questionsQuestion.findMany({
cursor: cursor ? { id: cursor } : undefined,
include: {
_count: {
select: {
answers: true,
comments: true,
},
},
encounters: {
select: {
city: true,
company: true,
country: true,
role: true,
seenAt: true,
state: true,
},
},
user: {
select: {
name: true,
},
},
votes: true,
},
orderBy: sortCondition,
take: input.limit + 1,
where: {
id: input.content !== "" ? {
in: relatedQuestionsIdArray,
} : undefined,
...(input.questionTypes.length > 0
? {
questionType: {
in: input.questionTypes,
},
}
: {}),
encounters: {
some: {
seenAt: {
gte: input.startDate,
lte: input.endDate,
},
...(input.companyIds.length > 0
? {
company: {
id: {
in: input.companyIds,
},
},
}
: {}),
...(input.cityIds.length > 0
? {
city: {
id: {
in: input.cityIds,
},
},
}
: {}),
...(input.countryIds.length > 0
? {
country: {
id: {
in: input.countryIds,
},
},
}
: {}),
...(input.stateIds.length > 0
? {
state: {
id: {
in: input.stateIds,
},
},
}
: {}),
...(input.roles.length > 0
? {
role: {
in: input.roles,
},
}
: {}),
},
},
},
});
const processedQuestionsData = questionsData.map(
createQuestionWithAggregateData,
);
let nextCursor: typeof cursor | undefined = undefined;
if (questionsData.length > input.limit) {
const nextItem = questionsData.pop()!;
processedQuestionsData.pop();
const nextIdCursor: string | undefined = nextItem.id;
nextCursor = nextIdCursor;
}
return {
data: processedQuestionsData,
nextCursor,
};
},
});

Loading…
Cancel
Save