diff --git a/package.json b/package.json
index 1f6277adb91..883ebdd158f 100644
--- a/package.json
+++ b/package.json
@@ -127,7 +127,7 @@
"@emotion/eslint-plugin": "^11.11.0",
"@emotion/jest": "^11.11.0",
"@emotion/react": "^11.11.0",
- "@faker-js/faker": "^7.6.0",
+ "@faker-js/faker": "^8.0.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
"@storybook/addon-essentials": "^7.3.1",
"@storybook/addon-interactions": "^7.3.1",
diff --git a/src-docs/src/views/tables/in_memory/in_memory_search.tsx b/src-docs/src/views/tables/in_memory/in_memory_search.tsx
index 7d27f0e5c3a..f92b3558736 100644
--- a/src-docs/src/views/tables/in_memory/in_memory_search.tsx
+++ b/src-docs/src/views/tables/in_memory/in_memory_search.tsx
@@ -11,7 +11,6 @@ import {
EuiSpacer,
EuiSwitch,
EuiFlexGroup,
- EuiFlexItem,
EuiCallOut,
EuiCode,
} from '../../../../../src/components';
@@ -27,16 +26,23 @@ type User = {
};
const users: User[] = [];
+const usersWithSpecialCharacters: User[] = [];
for (let i = 0; i < 20; i++) {
- users.push({
+ const userData = {
id: i + 1,
- firstName: faker.name.firstName(),
- lastName: faker.name.lastName(),
+ firstName: faker.person.firstName(),
+ lastName: faker.person.lastName(),
github: faker.internet.userName(),
dateOfBirth: faker.date.past(),
online: faker.datatype.boolean(),
- location: faker.address.country(),
+ location: faker.location.country(),
+ };
+ users.push(userData);
+ usersWithSpecialCharacters.push({
+ ...userData,
+ firstName: `${userData.firstName} "${faker.string.symbol(10)}"`,
+ lastName: `${userData.lastName} ${faker.internet.emoji()}`,
});
}
@@ -108,6 +114,7 @@ export default () => {
const [incremental, setIncremental] = useState(false);
const [filters, setFilters] = useState(false);
const [contentBetween, setContentBetween] = useState(false);
+ const [textSearchFormat, setTextSearchFormat] = useState(false);
const search: EuiSearchBarProps = {
box: {
@@ -138,34 +145,34 @@ export default () => {
return (
<>
-
- setIncremental(!incremental)}
- />
-
-
- setFilters(!filters)}
- />
-
-
- setContentBetween(!contentBetween)}
- />
-
+ setIncremental(!incremental)}
+ />
+ setFilters(!filters)}
+ />
+ setContentBetween(!contentBetween)}
+ />
+ setTextSearchFormat(!textSearchFormat)}
+ />
{
expect(tableContent.at(2).text()).toBe('baz');
});
});
+
+ describe('text search format', () => {
+ it('allows searching for any text with special characters in it', () => {
+ const specialCharacterSearch =
+ '!@#$%^&*(){}+=-_hello:world"`<>?/👋~.,;|\\';
+ const items = [
+ { title: specialCharacterSearch },
+ { title: 'no special characters' },
+ ];
+ const columns = [{ field: 'title', name: 'Title' }];
+
+ const { getByTestSubject, container } = render(
+
+ );
+ fireEvent.keyUp(getByTestSubject('searchbox'), {
+ target: { value: specialCharacterSearch },
+ });
+
+ const tableContent = container.querySelectorAll(
+ '.euiTableRowCell .euiTableCellContent'
+ );
+ expect(tableContent).toHaveLength(1); // only 1 match
+ expect(tableContent[0]).toHaveTextContent(specialCharacterSearch);
+ });
+ });
});
diff --git a/src/components/basic_table/in_memory_table.tsx b/src/components/basic_table/in_memory_table.tsx
index dd606fd58d2..e146bcfe704 100644
--- a/src/components/basic_table/in_memory_table.tsx
+++ b/src/components/basic_table/in_memory_table.tsx
@@ -23,11 +23,15 @@ import { PropertySort } from '../../services';
import { Pagination as PaginationBarType } from './pagination_bar';
import { isString } from '../../services/predicate';
import { Comparators, Direction } from '../../services/sort';
-import { EuiSearchBar, Query } from '../search_bar';
+import {
+ EuiSearchBar,
+ EuiSearchBarProps,
+ Query,
+ SchemaType,
+} from '../search_bar/search_bar';
+import { EuiSearchBox } from '../search_bar/search_box';
import { EuiSpacer } from '../spacer';
import { CommonProps } from '../common';
-import { EuiSearchBarProps } from '../search_bar/search_bar';
-import { SchemaType } from '../search_bar/search_box';
import {
EuiTablePaginationProps,
euiTablePaginationDefaults,
@@ -76,6 +80,18 @@ type InMemoryTableProps = Omit<
* Configures #Search.
*/
search?: Search;
+ /**
+ * By default, tables use `eql` format for search which allows using advanced filters.
+ *
+ * However, certain special characters (such as quotes, parentheses, and colons)
+ * are reserved for EQL syntax and will error if used.
+ * If your table does not require filter search and instead requires searching for certain
+ * symbols, use a plain `text` search format instead (note that filters will be ignored
+ * in this format).
+ *
+ * @default "eql"
+ */
+ searchFormat?: 'eql' | 'text';
/**
* Configures #Pagination
*/
@@ -285,6 +301,7 @@ export class EuiInMemoryTable extends Component<
static defaultProps = {
responsive: true,
tableLayout: 'fixed',
+ searchFormat: 'eql',
};
tableRef: React.RefObject;
@@ -521,9 +538,34 @@ export class EuiInMemoryTable extends Component<
}));
};
+ // Alternative to onQueryChange - allows consumers to specify they want the
+ // search bar to ignore EQL syntax and only use the searchbar for plain text
+ onPlainTextSearch = (searchValue: string) => {
+ const escapedQueryText = searchValue.replace(/["\\]/g, '\\$&');
+ const finalQuery = `"${escapedQueryText}"`;
+ this.setState({
+ query: EuiSearchBar.Query.parse(finalQuery),
+ });
+ };
+
renderSearchBar() {
- const { search } = this.props;
- if (search) {
+ const { search, searchFormat } = this.props;
+ if (!search) return;
+
+ let searchBar: ReactNode;
+
+ if (searchFormat === 'text') {
+ const _searchBoxProps = (search as EuiSearchBarProps)?.box || {}; // Work around | boolean type
+ const { schema, ...searchBoxProps } = _searchBoxProps; // Destructure `schema` so it doesn't get rendered to DOM
+
+ searchBar = (
+
+ );
+ } else {
let searchBarProps: Omit = {};
if (isEuiSearchBarProps(search)) {
@@ -538,13 +580,17 @@ export class EuiInMemoryTable extends Component<
}
}
- return (
- <>
-
-
- >
+ searchBar = (
+
);
}
+
+ return (
+ <>
+ {searchBar}
+
+ >
+ );
}
resolveSearchSchema(): SchemaType {
@@ -653,6 +699,7 @@ export class EuiInMemoryTable extends Component<
tableLayout,
items: _unuseditems,
search,
+ searchFormat,
onTableChange,
executeQueryOptions,
allowNeutralSort,
diff --git a/src/components/search_bar/search_bar.tsx b/src/components/search_bar/search_bar.tsx
index a91f60c8b54..783440d90f0 100644
--- a/src/components/search_bar/search_bar.tsx
+++ b/src/components/search_bar/search_bar.tsx
@@ -11,7 +11,7 @@ import React, { Component, ReactElement } from 'react';
import { htmlIdGenerator } from '../../services/accessibility';
import { isString } from '../../services/predicate';
import { EuiFlexGroup, EuiFlexItem } from '../flex';
-import { EuiSearchBox, SchemaType } from './search_box';
+import { EuiSearchBox } from './search_box';
import { EuiSearchBarFilters, SearchFilterConfig } from './search_filters';
import { Query } from './query';
import { CommonProps } from '../common';
@@ -36,6 +36,12 @@ interface ArgsWithError {
error: Error;
}
+export interface SchemaType {
+ strict?: boolean;
+ fields?: any;
+ flags?: string[];
+}
+
export type EuiSearchBarOnChangeArgs = ArgsWithQuery | ArgsWithError;
type HintPopOverProps = Partial<
diff --git a/src/components/search_bar/search_box.tsx b/src/components/search_bar/search_box.tsx
index 85a78d3a726..403e9840da3 100644
--- a/src/components/search_bar/search_box.tsx
+++ b/src/components/search_bar/search_box.tsx
@@ -6,21 +6,23 @@
* Side Public License, v 1.
*/
-import React, { Component } from 'react';
+import React, { FunctionComponent, useRef } from 'react';
+
+import { useUpdateEffect } from '../../services';
+import { useEuiI18n } from '../i18n';
import { EuiFieldSearch, EuiFieldSearchProps } from '../form';
import { EuiInputPopover } from '../popover';
-import { EuiSearchBarProps } from './search_bar';
-export interface SchemaType {
- strict?: boolean;
- fields?: any;
- flags?: string[];
-}
+import { EuiSearchBarProps } from './search_bar';
export interface EuiSearchBoxProps extends EuiFieldSearchProps {
query: string;
// This is optional in EuiFieldSearchProps
onSearch: (queryText: string) => void;
+ /**
+ * @default Search...
+ */
+ placeholder?: string;
hint?: {
id: string;
isVisible: boolean;
@@ -28,73 +30,73 @@ export interface EuiSearchBoxProps extends EuiFieldSearchProps {
} & EuiSearchBarProps['hint'];
}
-type DefaultProps = Pick;
+export const EuiSearchBox: FunctionComponent = ({
+ query,
+ placeholder,
+ incremental,
+ hint,
+ ...rest
+}) => {
+ const inputRef = useRef(null);
-export class EuiSearchBox extends Component {
- static defaultProps: DefaultProps = {
- placeholder: 'Search...',
- incremental: false,
- };
-
- private inputElement: HTMLInputElement | null = null;
-
- componentDidUpdate(oldProps: EuiSearchBoxProps) {
- if (oldProps.query !== this.props.query && this.inputElement != null) {
- this.inputElement.value = this.props.query;
- this.inputElement.dispatchEvent(new Event('change'));
+ useUpdateEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.value = query;
+ inputRef.current.dispatchEvent(new Event('change'));
}
- }
+ }, [query]);
- render() {
- const { query, incremental, hint, ...rest } = this.props;
+ const defaultPlaceholder = useEuiI18n(
+ 'euiSearchBox.placeholder',
+ 'Search...'
+ );
+ const ariaLabelIncremental = useEuiI18n(
+ 'euiSearchBox.incrementalAriaLabel',
+ 'This is a search bar. As you type, the results lower in the page will automatically filter.'
+ );
+ const ariaLabelEnter = useEuiI18n(
+ 'euiSearchBox.ariaLabel',
+ 'This is a search bar. After typing your query, hit enter to filter the results lower in the page.'
+ );
- let ariaLabel;
- if (incremental) {
- ariaLabel =
- 'This is a search bar. As you type, the results lower in the page will automatically filter.';
- } else {
- ariaLabel =
- 'This is a search bar. After typing your query, hit enter to filter the results lower in the page.';
- }
+ const search = (
+ (inputRef.current = input)}
+ fullWidth
+ defaultValue={query}
+ incremental={incremental}
+ aria-label={incremental ? ariaLabelIncremental : ariaLabelEnter}
+ placeholder={placeholder ?? defaultPlaceholder}
+ onFocus={() => {
+ hint?.setIsVisible(true);
+ }}
+ {...rest}
+ />
+ );
- const search = (
- (this.inputElement = input)}
+ if (hint) {
+ return (
+ {
- hint?.setIsVisible(true);
+ closePopover={() => {
+ hint.setIsVisible(false);
+ }}
+ panelProps={{
+ 'aria-live': undefined,
+ 'aria-modal': undefined,
+ role: undefined,
+ tabIndex: -1,
+ id: hint.id,
}}
- {...rest}
- />
+ {...hint.popoverProps}
+ >
+ {hint.content}
+
);
-
- if (hint) {
- return (
- {
- hint.setIsVisible(false);
- }}
- panelProps={{
- 'aria-live': undefined,
- 'aria-modal': undefined,
- role: undefined,
- tabIndex: -1,
- id: hint.id,
- }}
- {...hint.popoverProps}
- >
- {hint.content}
-
- );
- }
-
- return search;
}
-}
+
+ return search;
+};
diff --git a/upcoming_changelogs/7175.md b/upcoming_changelogs/7175.md
new file mode 100644
index 00000000000..b570f6c3d93
--- /dev/null
+++ b/upcoming_changelogs/7175.md
@@ -0,0 +1,5 @@
+- Updated `EuiInMemoryTable` with a new `searchFormat` prop (defaults to `eql`). When setting this prop to `text`, the built-in search bar will ignore EQL syntax and allow searching for plain strings with special characters and symbols.
+
+**Bug fixes**
+
+- Fixed missing i18n in `EuiSearchBar`'s default placeholder and aria-label text
diff --git a/yarn.lock b/yarn.lock
index 6d603d1194b..8ef2aad9d05 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2423,10 +2423,10 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0"
integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==
-"@faker-js/faker@^7.6.0":
- version "7.6.0"
- resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.6.0.tgz#9ea331766084288634a9247fcd8b84f16ff4ba07"
- integrity sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==
+"@faker-js/faker@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.0.2.tgz#bab698c5d3da9c52744e966e0e3eedb6c8b05c37"
+ integrity sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==
"@fal-works/esbuild-plugin-global-externals@^2.1.2":
version "2.1.2"