Skip to content

Commit

Permalink
Merge pull request #653 from thepolicylab-projectportals/476-feat-add…
Browse files Browse the repository at this point in the history
…-full-site-search-lunr

feat: 476 add full site search
  • Loading branch information
hetd54 authored Nov 22, 2023
2 parents 0aecb93 + d73ff7e commit 37e171f
Show file tree
Hide file tree
Showing 23 changed files with 964 additions and 32 deletions.
49 changes: 42 additions & 7 deletions __tests__/gatsby-theme-project-portal/SearchBar.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ describe("SearchBar", () => {

it("renders label and input elements with the correct props", () => {
const { getByLabelText, getByText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//looking for text set to "Test Label"
expect(getByText("Test Label")).toBeInTheDocument()
Expand All @@ -18,7 +23,12 @@ describe("SearchBar", () => {

it("calls the onChange callback when the input value changes", () => {
const { getByLabelText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//fireEvent.change to simulate the input value change
//trigger's a change event on the input element
Expand All @@ -30,23 +40,38 @@ describe("SearchBar", () => {

it("renders the input placeholder correctly", () => {
const { getByPlaceholderText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//check placeholder text
expect(getByPlaceholderText("Type to filter posts...")).toBeInTheDocument()
})

it("sets the aria-label attribute to the correct value", () => {
const { getByLabelText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//checks that aria-label is set correctly
expect(getByLabelText("Search")).toHaveAttribute("aria-label", "Search")
})

it("applies the correct CSS classes to the input element", () => {
const { getByLabelText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//checks that tailwind styling is correct
expect(getByLabelText("Search")).toHaveClass(
Expand All @@ -58,15 +83,25 @@ describe("SearchBar", () => {

it("sets the htmlFor attribute on the label element", () => {
const { getByText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
//check rendered component has attribute id = search-label
expect(getByText("Test Label")).toHaveAttribute("id", "search-label")
})

it("applies the style prop correctly to the input element", () => {
const { getByLabelText } = render(
<SearchBar label="Test Label" onChange={onChange} />
<SearchBar
label="Test Label"
onChange={onChange}
id={"search"}
placeholder={"Type to filter posts..."}
/>
)
expect(getByLabelText("Search")).toHaveStyle({ height: "62%" })
})
Expand Down
5 changes: 5 additions & 0 deletions packages/example-site/content/config/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"name": "Contact",
"link": "/contact/",
"show": true
},
{
"name": "Search",
"link": "/search/",
"show": true
}
]
},
Expand Down
5 changes: 5 additions & 0 deletions packages/example-site/content/page/search.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"templateKey": "SearchPage",
"title": "Search the Whole Site!",
"image": "background.jpg"
}
57 changes: 55 additions & 2 deletions packages/gatsby-theme-project-portal/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { withDefaults } = require(`./utils/default-options`)
const fs = require("fs")

const { createSearchIndex, searchQuery } = require(`./utils/search`)
const {
siteMetadataTypeDefs,
projectTypeDefs,
Expand Down Expand Up @@ -46,6 +46,13 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
}
}
}
searchPages: allGeneralPage(
filter: { templateKey: { eq: "SearchPage" } }
) {
nodes {
slug
}
}
aboutPages: allGeneralPage(filter: { templateKey: { eq: "AboutPage" } }) {
nodes {
slug
Expand All @@ -60,7 +67,6 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
}
}
`)

if (result.errors) {
reporter.panicOnBuild(result.errors)
}
Expand Down Expand Up @@ -97,6 +103,18 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
})
})

const { searchPages } = result.data
searchPages.nodes.forEach((page) => {
const { slug } = page
createPage({
path: `/${slug}`,
component: require.resolve(`./src/layouts/SearchPageLayout.tsx`),
context: {
slug: slug,
},
})
})

const { aboutPages } = result.data
aboutPages.nodes.forEach((page) => {
const { slug } = page
Expand Down Expand Up @@ -129,3 +147,38 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
})
})
}

exports.onPreBuild = async ({ reporter, basePath, pathPrefix, graphql }) => {
const result = await graphql(searchQuery)
const { allProject, allGeneralPage } = result.data

const [index, documents] = createSearchIndex({ allProject, allGeneralPage })
await fs.writeFile("static/lunr-index.json", JSON.stringify(index), (err) => {
if (err) console.error(err)
})
await fs.writeFile(
"static/documents.json",
JSON.stringify(documents),
(err) => {
if (err) console.error(err)
}
)
// this is a function which grabs the page from the original documents
// lunr tosses this info for SPEED
const reduceDocuments = documents.reduce(function (page, document) {
page[document.slug] = document
return page
}, {})

await fs.writeFile(
"static/documents-reduced.json",
JSON.stringify(reduceDocuments),
(err) => {
if (err) console.error(err)
}
)

reporter.info(
`Site was built with basePath: ${basePath} & pathPrefix: ${pathPrefix}`
)
}
1 change: 1 addition & 0 deletions packages/gatsby-theme-project-portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"gatsby-transformer-sharp": "^5.12.3",
"js-search": "^2.0.1",
"lodash": "^4.17.21",
"lunr": "^2.3.9",
"markdown-to-jsx": "^7.3.2",
"moment": "^2.29.4",
"react-google-recaptcha": "^3.1.0",
Expand Down
16 changes: 16 additions & 0 deletions packages/gatsby-theme-project-portal/src/components/Label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { FunctionComponent } from "react"

interface LabelProps {
id: string
label: string
}

export const Label: FunctionComponent<LabelProps> = ({ id, label }) => {
return (
<>
<label id={`${id}-label`} className="font-bold p-1" htmlFor={id}>
{label}
</label>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,78 @@ export const Primary: Story = {
},
}

export const LongTitle: Story = {
args: {
title: "completed 2",
agency: "example agency",
topics: [],
slug: "project/completed-project",
summary: "example project summary",
statusOfData: "example statusOfData",
status: "completed",
startDate: new Date("2022-06-17"),
requirement: "example requirement",
question:
"Hello world2 (from json)? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua?",
purpose: "example purpose",
projectTeam: ProjectTeamStories.Primary.args.contacts,
priorResearch: null,
opportunityCloses: new Date("2022-10-28"),
mainContact: {
name: "Contact Name",
title: "Title",
employer: "Employer",
email: "[email protected]",
image: contactImageYogi,
},
fundingInfo: "example fundingInfo",
expertise: "example expertise",
faq: [
{ title: "Question 1?", text: "Answer 1!" },
{
title: "This is another different question?",
text: "Here's an answer for that question! Yay!",
},
],
deliverable: "example deliverable",
emailContent: "example emailContent",
endDate: new Date("2016-12-15"),
},
}

export const NullValues: Story = {
args: {
title: "project completed 2",
agency: "example agency",
topics: [],
slug: "project/completed-project2",
summary: "",
statusOfData: null,
status: "completed",
startDate: new Date("2022-06-17"),
requirement: null,
question: "",
purpose: null,
projectTeam: ProjectTeamStories.Primary.args.contacts,
priorResearch: null,
opportunityCloses: new Date("2022-10-28"),
mainContact: {
name: "Contact Name",
title: "Title",
employer: "Employer",
email: "[email protected]",
image: contactImageYogi,
},
keyDates: null,
fundingInfo: null,
expertise: null,
faq: null,
deliverable: null,
emailContent: null,
endDate: new Date("2016-12-15"),
},
}

export const Open: Story = {
args: { ...Primary.args, status: "open" },
}
Expand Down Expand Up @@ -93,6 +165,58 @@ export const Minimum: Story = {
},
}

export const Maximum: Story = {
args: {
question: "Is there a difference between brown sugar and white sugar?",
summary:
"Contrary to common belief, they are nutritionally similar. Nutritionally speaking, all natural sugars have relatively comparable nutritional value with approximately 15 calories per teaspoon (4.2 g).\n",
title: "Sugar Investigations in the US",
mainContact: {
name: "Contact Name",
title: "Title",
employer: "Employer",
email: "[email protected]",
},

status: "open",
opportunityCloses: new Date("2022-03-04"),
startDate: new Date("2022-01-03"),
endDate: new Date("2022-03-04"),
agency: "Sugar Agency",
topics: [
{ slug: "conspiracy", title: "conspiracy" },
{ slug: "investigation", title: "investigation" },
],
lastModified: new Date("2022-05-27T16:34:04.000Z"),
slug: "project/sugar-investigations",
emailContent:
"Dearest community, Are you interested in knowing about sugars? So are we! Join our search!",
deliverable:
"Analysis of existing sugars in the United States. Summary of and detailed recommendations and potential action steps to inform the public about the sugars they are consuming.  \\n* See project summary for additional information.",
purpose:
"Results will be shared with the FDA and whoever else wants these results. We have no oversight and thus may do as we wish.",
expertise:
"While our team does not have previous experience in organic chemistry, we are committed to uncovering the truth.",
requirement: "I do not understand this question. Next.",
keyDates: "Publish data at some point in time.",
priorResearch: "Prior research is published in many reputable journals.",
statusOfData: "Data collection has not begun.",
fundingInfo:
"Our team will evaluate whether or not a project needs money, and we will most likely need the project team to figure out funding.",
openText: "Are you interested in collaborating?",
ongoingText: "This project is ongoing.",
completeText: "This project is complete.",
projectTeam: ProjectTeamStories.Primary.args.contacts,
faq: [
{ title: "Question 1?", text: "Answer 1!" },
{
title: "This is another question?",
text: "Here's an answer for that question! Yay!",
},
],
},
}

export const TailwindXSWindow: Story = {
args: Primary.args,
parameters: { viewport: { defaultViewport: "tailwindXS" } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { statusOutput, isNA, isEmpty } from "../utils"
export interface ProjectDetailProps {
// Core content
question: string
slug?: string
summary: string
title: string

Expand Down Expand Up @@ -57,17 +58,19 @@ export interface ProjectDetailProps {
agency: string
topics?: TopicType[]
lastModified: Date

emailContent?: String
defaultContactImage: ImageDataLike
}

export const ProjectDetail: FunctionComponent<ProjectDetailProps> = ({
question,
slug,
summary,
status,
opportunityCloses,
startDate,
endDate,
emailContent,
lastModified,
agency,
topics,
Expand Down
Loading

0 comments on commit 37e171f

Please sign in to comment.