Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/reusables/repositories/rulesets-commit-regex.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ When you add metadata restrictions, you can use regular expression syntax to def

Rulesets support RE2 syntax. For more information, see Google's [syntax guide](https://github.com/google/re2/wiki/Syntax). To validate your expressions, you can use the validator on [regex101.com](https://regex101.com/), selecting the "Golang" flavor in the left sidebar.

Regular expressions consider multiple lines of text by default. For example, if you have a multiline commit message, the pattern `^ABC` will be a match if any line in the message starts with `ABC`. To match the start of the message specifically, you can start your expression with `\A`.
By default, regular expressions in metadata restrictions do not consider multiple lines of text. For example, if you have a multiline commit message, the pattern `^ABC` will be a match if the first line of the message starts with `ABC`. To match multiple lines of the message, start your expression with `(?m)`.

The negative lookahead assertion, denoted `?!`, is not supported. However, for cases where you need to look for a given string that is not followed by another given string, you can use the positive lookahead assertion, denoted `?`, combined with the "Must not match a given regex pattern" requirement.

Expand Down
18 changes: 8 additions & 10 deletions src/search/components/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,41 @@ import { SearchIcon } from '@primer/octicons-react'
import { useRouter } from 'next/router'
import cx from 'classnames'

import type { SearchResultsT, SearchResultHitT } from './types'
import type { SearchResultsT, SearchResultHitT, SearchQueryT } from './types'
import { useTranslation } from 'components/hooks/useTranslation'
import { Link } from 'components/Link'
import { useQuery } from 'src/search/components/useQuery'
import { sendEvent, EventType } from 'src/events/components/events'

import styles from './SearchResults.module.scss'

type Props = {
results: SearchResultsT
query: string
search: SearchQueryT
}
export function SearchResults({ results, query }: Props) {
export function SearchResults({ results, search }: Props) {
const pages = Math.ceil(results.meta.found.value / results.meta.size)
const { page } = results.meta

return (
<div>
<SearchResultHits hits={results.hits} query={query} />
<SearchResultHits hits={results.hits} search={search} />
{pages > 1 && <ResultsPagination page={page} totalPages={pages} />}
</div>
)
}

function SearchResultHits({ hits, query }: { hits: SearchResultHitT[]; query: string }) {
const { debug } = useQuery()
function SearchResultHits({ hits, search }: { hits: SearchResultHitT[]; search: SearchQueryT }) {
return (
<div>
{hits.length === 0 && <NoSearchResults />}
{hits.map((hit, index) => (
<SearchResultHit
key={hit.id}
hit={hit}
query={query}
query={search.query}
totalHits={hits.length}
index={index}
debug={debug}
debug={search.debug}
/>
))}
</div>
Expand Down Expand Up @@ -117,7 +115,7 @@ function SearchResultHit({
function ResultsPagination({ page, totalPages }: { page: number; totalPages: number }) {
const router = useRouter()

const [asPathRoot, asPathQuery = ''] = router.asPath.split('?')
const [asPathRoot, asPathQuery = ''] = router.asPath.split('#')[0].split('?')

function hrefBuilder(page: number) {
const params = new URLSearchParams(asPathQuery)
Expand Down
2 changes: 1 addition & 1 deletion src/search/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function Search({ search }: Props) {
<ValidationErrors errors={validationErrors} />
) : null}

{results ? <SearchResults results={results} query={query} /> : null}
{results ? <SearchResults results={results} search={search.search} /> : null}
</div>
)
}
11 changes: 1 addition & 10 deletions src/search/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,12 @@ export type SearchResultsT = {

export type SearchQueryT = {
query: string
version: string
language: string
size: number
page: number
sort: string
highlights: string[]
autocomplete: boolean
debug: boolean
include: string[]
indexName: string
}

export type SearchValidationErrorT = {
error: string
key: string
// key: string
}

export type SearchT = {
Expand Down
5 changes: 4 additions & 1 deletion src/search/middleware/contextualize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import got from 'got'
import { errors } from '@elastic/elasticsearch'
import statsd from '#src/observability/lib/statsd.js'

import { getPathWithoutVersion, getPathWithoutLanguage } from '../../../lib/path-utils.js'
import { getSearchFromRequest } from './get-search-request.js'
Expand Down Expand Up @@ -61,8 +62,10 @@ export default async function contextualizeSearch(req, res, next) {
// In local dev, you get to see the error. In production,
// you get a "Oops! Something went wrong" which involves a Failbot
// send.
const tags = [`indexName:${search.indexName}`]
const timed = statsd.asyncTimer(getSearchResults, 'contextualize.search', tags)
try {
req.context.search.results = await getSearchResults(search)
req.context.search.results = await timed(search)
} catch (error) {
// If the error coming from the Elasticsearch client is any sort
// of 4xx error, it will be bubbled up to the next middleware
Expand Down
26 changes: 25 additions & 1 deletion src/search/pages/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,31 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
// This should have been done by the middleware.
throw new Error('Expected req.context to be populated with .search')
}
const search: SearchT = req.context.search

// The `req.context.search` is similar to what's needed to React
// render the search result page.
// But it contains information (from the contextualizing) that is
// not needed to display search results.
// For example, the `req.context.search.search` contains things like
// `page` and `indexName` which was useful when it made the actual
// Elasticsearch query. But it's not needed to render the results.
// We explicitly pick out the parts that are needed, only.
const search: SearchT = {
search: {
query: req.context.search.search.query,
debug: req.context.search.search.debug,
},
validationErrors: req.context.search.validationErrors,
}
// If there are no results (e.g. /en/search?query=) from the
// contextualizing, then `req.context.search.results` will
// be `undefined` which can't be serialized as a prop, using JSON.stringify.
if (req.context.search.results) {
search.results = {
meta: req.context.search.results.meta,
hits: req.context.search.results.hits,
}
}

return {
props: {
Expand Down