Skip to content

Commit

Permalink
change: [M3-8530] - Allow quoted strings in Search v2 and improve Sta…
Browse files Browse the repository at this point in the history
…ckScript searching on Linode Create v2 (#10894)

* allow users to search with quoted strings / spaces

* add changesets

* Apply suggestions from code review

Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>

* improve search tooltip

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 9, 2024
1 parent 516ae68 commit b8f471a
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 25 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10894-added-1725553004847.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Support for quoted strings in Search v2 ([#10894](https://github.com/linode/manager/pull/10894))
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10894-fixed-1725552964331.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Search queries containing `and` on Linode Create v2's StackScript tab not being respected ([#10894](https://github.com/linode/manager/pull/10894))
1 change: 1 addition & 0 deletions packages/manager/src/components/Code/Code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const Code = (props: Props) => {

const StyledSpan = styled('span')(({ theme }) => ({
backgroundColor: theme.color.grey5,
borderRadius: theme.spacing(0.3),
color: theme.color.black,
fontFamily: '"Ubuntu Mono", monospace, sans-serif',
margin: '0 2px',
Expand Down
3 changes: 3 additions & 0 deletions packages/manager/src/components/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ interface InputToolTipProps {
tooltipOnMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
tooltipPosition?: TooltipProps['placement'];
tooltipText?: JSX.Element | string;
tooltipWidth?: number;
}

interface TextFieldPropsOverrides extends StandardTextFieldProps {
Expand Down Expand Up @@ -253,6 +254,7 @@ export const TextField = (props: TextFieldProps) => {
tooltipOnMouseEnter,
tooltipPosition,
tooltipText,
tooltipWidth,
trimmed,
type,
value,
Expand Down Expand Up @@ -486,6 +488,7 @@ export const TextField = (props: TextFieldProps) => {
status="help"
text={tooltipText}
tooltipPosition={tooltipPosition}
width={tooltipWidth}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { debounce } from 'throttle-debounce';
import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { Code } from 'src/components/Code/Code';
import { IconButton } from 'src/components/IconButton';
import { InputAdornment } from 'src/components/InputAdornment';
import { Stack } from 'src/components/Stack';
Expand All @@ -23,6 +24,7 @@ import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading'
import { TableSortCell } from 'src/components/TableSortCell';
import { TextField } from 'src/components/TextField';
import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';
import { useOrder } from 'src/hooks/useOrder';
import {
useStackScriptQuery,
Expand Down Expand Up @@ -107,8 +109,8 @@ export const StackScriptSelectionList = ({ type }: Props) => {
{
['+order']: order,
['+order_by']: orderBy,
...searchFilter,
...filter,
...searchFilter,
},
!hasPreselectedStackScript
);
Expand Down Expand Up @@ -173,15 +175,36 @@ export const StackScriptSelectionList = ({ type }: Props) => {
),
}}
tooltipText={
type === 'Community'
? 'Hint: try searching for a specific item by prepending your search term with "username:", "label:", or "description:"'
: undefined
<Stack spacing={1}>
<Typography>
You can search for a specific item by prepending your search term
with "username:", "label:", or "description:".
</Typography>
<Box>
<Typography fontFamily={(theme) => theme.font.bold}>
Examples
</Typography>
<Typography fontSize="0.8rem">
<Code>username: linode</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>label: sql</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>description: "ubuntu server"</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>label: sql or label: php</Code>
</Typography>
</Box>
</Stack>
}
hideLabel
label="Search"
onChange={debounce(400, (e) => setQuery(e.target.value))}
placeholder="Search StackScripts"
spellCheck={false}
tooltipWidth={300}
value={query}
/>
<Table sx={{ mt: 1 }}>
Expand Down
51 changes: 31 additions & 20 deletions packages/search/src/search.peggy
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ subQuery
/ LessThenOrEqualTo
/ GreaterThanQuery
/ GreaterThanOrEqualTo

DefaultQuery
= input:String {
const keys = options.searchableFieldsWithoutOperator;
Expand All @@ -29,27 +29,27 @@ DefaultQuery
EqualQuery
= key:FilterableField ws* Equal ws* value:Number { return { [key]: value }; }
/ key:FilterableField ws* Equal ws* value:String { return { [key]: value }; }

ContainsQuery
= key:FilterableField ws* Contains ws* value:String { return { [key]: { "+contains": value } }; }

TagQuery
= "tag" ws* Equal ws* value:String { return { "tags": { "+contains": value } }; }
= "tag" ws* Equal ws* value:String { return { "tags": { "+contains": value } }; }

NotEqualQuery
= Not key:FilterableField ws* Equal ws* value:String { return { [key]: { "+neq": value } }; }

LessThanQuery
= key:FilterableField ws* Less ws* value:Number { return { [key]: { "+lt": value } }; }
= key:FilterableField ws* Less ws* value:Number { return { [key]: { "+lt": value } }; }

GreaterThanQuery
= key:FilterableField ws* Greater ws* value:Number { return { [key]: { "+gt": value } }; }
= key:FilterableField ws* Greater ws* value:Number { return { [key]: { "+gt": value } }; }

GreaterThanOrEqualTo
= key:FilterableField ws* Gte ws* value:Number { return { [key]: { "+gte": value } }; }
= key:FilterableField ws* Gte ws* value:Number { return { [key]: { "+gte": value } }; }

LessThenOrEqualTo
= key:FilterableField ws* Lte ws* value:Number { return { [key]: { "+lte": value } }; }
= key:FilterableField ws* Lte ws* value:Number { return { [key]: { "+lte": value } }; }

Or
= ws+ 'or'i ws+
Expand All @@ -65,7 +65,7 @@ And
Not
= '!'
/ '-'

Less
= '<'

Expand All @@ -74,26 +74,37 @@ Greater

Gte
= '>='

Lte
= '<='

Equal
= "="

Contains
= "~"
/ ":"

= "~"
/ ":"

Quote
= "\""
/ "\'"

FilterableField "filterable field"
= [a-zA-Z0-9\-\.]+ { return text(); }

String "search value"
= Quote value:StringWithSpaces Quote { return value }
/ Word

Word "word"
= [a-zA-Z0-9\-\.]+ { return text(); }


StringWithSpaces "string with spaces"
= [a-zA-Z0-9\-\. ]+ { return text(); }

Number "numeric search value"
= number:[0-9\.]+ { return parseFloat(number.join("")); }
/ number:[0-9]+ { return parseInt(number.join(""), 10); }
= number:[0-9\.]+ { return parseFloat(number.join("")); }
/ number:[0-9]+ { return parseInt(number.join(""), 10); }

ws "whitespace"
= [ \t\r\n]
= [ \t\r\n]
27 changes: 26 additions & 1 deletion packages/search/src/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,29 @@ describe("getAPIFilterFromQuery", () => {
getAPIFilterFromQuery(query, { searchableFieldsWithoutOperator: [] }).error?.message
).toEqual("Expected search value or whitespace but end of input found.");
});
});

it("allows a quoted string so you can use spaces in the query (double quotes)", () => {
const query = 'label: "my stackscript"';

expect(getAPIFilterFromQuery(query, { searchableFieldsWithoutOperator: [] })).toEqual({
filter: {
label: { '+contains': "my stackscript" }
},
error: null,
});
});

it("allows a quoted string so you can use spaces in the query (single quotes)", () => {
const query = "label: 'my stackscript' and username = linode";

expect(getAPIFilterFromQuery(query, { searchableFieldsWithoutOperator: [] })).toEqual({
filter: {
["+and"]: [
{ label: { "+contains": "my stackscript" } },
{ username: "linode" },
],
},
error: null,
});
});
});

0 comments on commit b8f471a

Please sign in to comment.