@backstage/plugin-search-react 1.1.0-next.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +356 -0
- package/dist/index.d.ts +314 -14
- package/dist/index.esm.js +353 -43
- package/dist/index.esm.js.map +1 -1
- package/package.json +10 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,361 @@
|
|
|
1
1
|
# @backstage/plugin-search-react
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 97f2b8f3fd: The `<SearchResult/>` component now accepts a optional `query` prop to request results from the search api:
|
|
8
|
+
|
|
9
|
+
> Note: If a query prop is not defined, the results will by default be consumed from the context.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
|
|
13
|
+
```jsx
|
|
14
|
+
import React, { useState, useCallback } from 'react';
|
|
15
|
+
|
|
16
|
+
import { Grid, List, Paper } from '@material-ui/core';
|
|
17
|
+
|
|
18
|
+
import { Page, Header, Content, Lifecycle } from '@backstage/core-components';
|
|
19
|
+
import {
|
|
20
|
+
DefaultResultListItem,
|
|
21
|
+
SearchBarBase,
|
|
22
|
+
SearchResult,
|
|
23
|
+
} from '@backstage/plugin-search-react';
|
|
24
|
+
|
|
25
|
+
const SearchPage = () => {
|
|
26
|
+
const [query, setQuery] = useState({
|
|
27
|
+
term: '',
|
|
28
|
+
types: [],
|
|
29
|
+
filters: {},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const handleChange = useCallback(
|
|
33
|
+
(term: string) => {
|
|
34
|
+
setQuery(prevQuery => ({ ...prevQuery, term }));
|
|
35
|
+
},
|
|
36
|
+
[setQuery],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Page themeId="home">
|
|
41
|
+
<Header title="Search" subtitle={<Lifecycle alpha />} />
|
|
42
|
+
<Content>
|
|
43
|
+
<Grid container direction="row">
|
|
44
|
+
<Grid item xs={12}>
|
|
45
|
+
<Paper>
|
|
46
|
+
<SearchBarBase debounceTime={100} onChange={handleChange} />
|
|
47
|
+
</Paper>
|
|
48
|
+
</Grid>
|
|
49
|
+
<Grid item xs>
|
|
50
|
+
<SearchResult query={query}>
|
|
51
|
+
{({ results }) => (
|
|
52
|
+
<List>
|
|
53
|
+
{results.map(({ document }) => (
|
|
54
|
+
<DefaultResultListItem
|
|
55
|
+
key={document.location}
|
|
56
|
+
result={document}
|
|
57
|
+
/>
|
|
58
|
+
))}
|
|
59
|
+
</List>
|
|
60
|
+
)}
|
|
61
|
+
</SearchResult>
|
|
62
|
+
</Grid>
|
|
63
|
+
</Grid>
|
|
64
|
+
</Content>
|
|
65
|
+
</Page>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Additionally, a search page can also be composed using these two new results layout components:
|
|
71
|
+
|
|
72
|
+
```jsx
|
|
73
|
+
// Example rendering results as list
|
|
74
|
+
<SearchResult>
|
|
75
|
+
{({ results }) => (
|
|
76
|
+
<SearchResultListLayout
|
|
77
|
+
resultItems={results}
|
|
78
|
+
renderResultItem={({ type, document }) => {
|
|
79
|
+
switch (type) {
|
|
80
|
+
case 'custom-result-item':
|
|
81
|
+
return (
|
|
82
|
+
<CustomResultListItem
|
|
83
|
+
key={document.location}
|
|
84
|
+
result={document}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
default:
|
|
88
|
+
return (
|
|
89
|
+
<DefaultResultListItem
|
|
90
|
+
key={document.location}
|
|
91
|
+
result={document}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</SearchResult>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```jsx
|
|
102
|
+
// Example rendering results as groups
|
|
103
|
+
<SearchResult>
|
|
104
|
+
{({ results }) => (
|
|
105
|
+
<>
|
|
106
|
+
<SearchResultGroupLayout
|
|
107
|
+
icon={<CustomIcon />}
|
|
108
|
+
title="Custom"
|
|
109
|
+
link="See all custom results"
|
|
110
|
+
resultItems={results.filter(
|
|
111
|
+
({ type }) => type === 'custom-result-item',
|
|
112
|
+
)}
|
|
113
|
+
renderResultItem={({ document }) => (
|
|
114
|
+
<CustomResultListItem key={document.location} result={document} />
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
<SearchResultGroupLayout
|
|
118
|
+
icon={<DefaultIcon />}
|
|
119
|
+
title="Default"
|
|
120
|
+
resultItems={results.filter(
|
|
121
|
+
({ type }) => type !== 'custom-result-item',
|
|
122
|
+
)}
|
|
123
|
+
renderResultItem={({ document }) => (
|
|
124
|
+
<DefaultResultListItem key={document.location} result={document} />
|
|
125
|
+
)}
|
|
126
|
+
/>
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
</SearchResult>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A `SearchResultList` and `SearchResultGroup` components were also created for users who have search pages with multiple queries, both are specializations of `SearchResult` and also accept a `query` as a prop as well:
|
|
133
|
+
|
|
134
|
+
```jsx
|
|
135
|
+
// Example using the <SearchResultList />
|
|
136
|
+
const SearchPage = () => {
|
|
137
|
+
const query = {
|
|
138
|
+
term: 'example',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<SearchResultList
|
|
143
|
+
query={query}
|
|
144
|
+
renderResultItem={({ type, document, highlight, rank }) => {
|
|
145
|
+
switch (type) {
|
|
146
|
+
case 'custom':
|
|
147
|
+
return (
|
|
148
|
+
<CustomResultListItem
|
|
149
|
+
key={document.location}
|
|
150
|
+
icon={<CatalogIcon />}
|
|
151
|
+
result={document}
|
|
152
|
+
highlight={highlight}
|
|
153
|
+
rank={rank}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
default:
|
|
157
|
+
return (
|
|
158
|
+
<DefaultResultListItem
|
|
159
|
+
key={document.location}
|
|
160
|
+
result={document}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```jsx
|
|
171
|
+
// Example using the <SearchResultGroup /> for creating a component that search and group software catalog results
|
|
172
|
+
import React, { useState, useCallback } from 'react';
|
|
173
|
+
|
|
174
|
+
import { MenuItem } from '@material-ui/core';
|
|
175
|
+
|
|
176
|
+
import { JsonValue } from '@backstage/types';
|
|
177
|
+
import { CatalogIcon } from '@backstage/core-components';
|
|
178
|
+
import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
|
|
179
|
+
import {
|
|
180
|
+
SearchResultGroup,
|
|
181
|
+
SearchResultGroupTextFilterField,
|
|
182
|
+
SearchResultGroupSelectFilterField,
|
|
183
|
+
} from @backstage/plugin-search-react;
|
|
184
|
+
import { SearchQuery } from '@backstage/plugin-search-common';
|
|
185
|
+
|
|
186
|
+
const CatalogResultsGroup = () => {
|
|
187
|
+
const [query, setQuery] = useState<Partial<SearchQuery>>({
|
|
188
|
+
types: ['software-catalog'],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const filterOptions = [
|
|
192
|
+
{
|
|
193
|
+
label: 'Lifecycle',
|
|
194
|
+
value: 'lifecycle',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
label: 'Owner',
|
|
198
|
+
value: 'owner',
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const handleFilterAdd = useCallback(
|
|
203
|
+
(key: string) => () => {
|
|
204
|
+
setQuery(prevQuery => {
|
|
205
|
+
const { filters: prevFilters, ...rest } = prevQuery;
|
|
206
|
+
const newFilters = { ...prevFilters, [key]: undefined };
|
|
207
|
+
return { ...rest, filters: newFilters };
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
[],
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const handleFilterChange = useCallback(
|
|
214
|
+
(key: string) => (value: JsonValue) => {
|
|
215
|
+
setQuery(prevQuery => {
|
|
216
|
+
const { filters: prevFilters, ...rest } = prevQuery;
|
|
217
|
+
const newFilters = { ...prevFilters, [key]: value };
|
|
218
|
+
return { ...rest, filters: newFilters };
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
[],
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const handleFilterDelete = useCallback(
|
|
225
|
+
(key: string) => () => {
|
|
226
|
+
setQuery(prevQuery => {
|
|
227
|
+
const { filters: prevFilters, ...rest } = prevQuery;
|
|
228
|
+
const newFilters = { ...prevFilters };
|
|
229
|
+
delete newFilters[key];
|
|
230
|
+
return { ...rest, filters: newFilters };
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
[],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<SearchResultGroup
|
|
238
|
+
query={query}
|
|
239
|
+
icon={<CatalogIcon />}
|
|
240
|
+
title="Software Catalog"
|
|
241
|
+
link="See all software catalog results"
|
|
242
|
+
filterOptions={filterOptions}
|
|
243
|
+
renderFilterOption={({ label, value }) => (
|
|
244
|
+
<MenuItem key={value} onClick={handleFilterAdd(value)}>
|
|
245
|
+
{label}
|
|
246
|
+
</MenuItem>
|
|
247
|
+
)}
|
|
248
|
+
renderFilterField={(key: string) => {
|
|
249
|
+
switch (key) {
|
|
250
|
+
case 'lifecycle':
|
|
251
|
+
return (
|
|
252
|
+
<SearchResultGroupSelectFilterField
|
|
253
|
+
key={key}
|
|
254
|
+
label="Lifecycle"
|
|
255
|
+
value={query.filters?.lifecycle}
|
|
256
|
+
onChange={handleFilterChange('lifecycle')}
|
|
257
|
+
onDelete={handleFilterDelete('lifecycle')}
|
|
258
|
+
>
|
|
259
|
+
<MenuItem value="production">Production</MenuItem>
|
|
260
|
+
<MenuItem value="experimental">Experimental</MenuItem>
|
|
261
|
+
</SearchResultGroupSelectFilterField>
|
|
262
|
+
);
|
|
263
|
+
case 'owner':
|
|
264
|
+
return (
|
|
265
|
+
<SearchResultGroupTextFilterField
|
|
266
|
+
key={key}
|
|
267
|
+
label="Owner"
|
|
268
|
+
value={query.filters?.owner}
|
|
269
|
+
onChange={handleFilterChange('owner')}
|
|
270
|
+
onDelete={handleFilterDelete('owner')}
|
|
271
|
+
/>
|
|
272
|
+
);
|
|
273
|
+
default:
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
renderResultItem={({ document, highlight, rank }) => (
|
|
278
|
+
<CatalogSearchResultListItem
|
|
279
|
+
key={document.location}
|
|
280
|
+
result={document}
|
|
281
|
+
highlight={highlight}
|
|
282
|
+
rank={rank}
|
|
283
|
+
/>
|
|
284
|
+
)}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- 18f60427f2: Provides search autocomplete functionality through a `SearchAutocomplete` component.
|
|
291
|
+
A `SearchAutocompleteDefaultOption` can also be used to render options with icons, primary texts, and secondary texts.
|
|
292
|
+
Example:
|
|
293
|
+
|
|
294
|
+
```jsx
|
|
295
|
+
import React, { ChangeEvent, useState, useCallback } from 'react';
|
|
296
|
+
import useAsync from 'react-use/lib/useAsync';
|
|
297
|
+
|
|
298
|
+
import { Grid, Paper } from '@material-ui/core';
|
|
299
|
+
|
|
300
|
+
import { Page, Content } from '@backstage/core-components';
|
|
301
|
+
import { SearchAutocomplete, SearchAutocompleteDefaultOption} from '@backstage/plugin-search-react';
|
|
302
|
+
|
|
303
|
+
const OptionsIcon = () => <svg />
|
|
304
|
+
|
|
305
|
+
const SearchPage = () => {
|
|
306
|
+
const [inputValue, setInputValue] = useState('');
|
|
307
|
+
|
|
308
|
+
const options = useAsync(async () => {
|
|
309
|
+
// Gets and returns autocomplete options
|
|
310
|
+
}, [inputValue])
|
|
311
|
+
|
|
312
|
+
const useCallback((_event: ChangeEvent<{}>, newInputValue: string) => {
|
|
313
|
+
setInputValue(newInputValue);
|
|
314
|
+
}, [setInputValue])
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<Page themeId="home">
|
|
318
|
+
<Content>
|
|
319
|
+
<Grid container direction="row">
|
|
320
|
+
<Grid item xs={12}>
|
|
321
|
+
<Paper>
|
|
322
|
+
<SearchAutocomplete
|
|
323
|
+
options={options}
|
|
324
|
+
inputValue={inputValue}
|
|
325
|
+
inputDebounceTime={100}
|
|
326
|
+
onInputChange={handleInputChange}
|
|
327
|
+
getOptionLabel={option => option.title}
|
|
328
|
+
renderOption={option => (
|
|
329
|
+
<SearchAutocompleteDefaultOption
|
|
330
|
+
icon={<OptionIcon />}
|
|
331
|
+
primaryText={option.title}
|
|
332
|
+
secondaryText={option.text}
|
|
333
|
+
/>
|
|
334
|
+
)}
|
|
335
|
+
/>
|
|
336
|
+
</Paper>
|
|
337
|
+
</Grid>
|
|
338
|
+
</Grid>
|
|
339
|
+
{'/* Filters and results are omitted */'}
|
|
340
|
+
</Content>
|
|
341
|
+
</Page>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- ca8d5a6eae: We noticed a repeated check for the existence of a parent context before creating a child search context in more the one component such as Search Modal and Search Bar and to remove code duplication we extract the conditional to the context provider, now you can use it passing an `inheritParentContextIfAvailable` prop to the `SearchContextProvider`.
|
|
347
|
+
|
|
348
|
+
Note: This added property does not create a local context if there is a parent context and in this case, you cannot use it together with `initialState`, it will result in a type error because the parent context is already initialized.
|
|
349
|
+
|
|
350
|
+
### Patch Changes
|
|
351
|
+
|
|
352
|
+
- 817f3196f6: Updated React Router dependencies to be peer dependencies.
|
|
353
|
+
- d3737da337: Reset page cursor on search filter change
|
|
354
|
+
- Updated dependencies
|
|
355
|
+
- @backstage/core-components@0.11.1
|
|
356
|
+
- @backstage/core-plugin-api@1.0.6
|
|
357
|
+
- @backstage/plugin-search-common@1.0.1
|
|
358
|
+
|
|
3
359
|
## 1.1.0-next.2
|
|
4
360
|
|
|
5
361
|
### Minor Changes
|