@backstage/plugin-search-react 1.0.2-next.1 → 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 +426 -0
- package/dist/index.d.ts +395 -35
- package/dist/index.esm.js +598 -154
- package/dist/index.esm.js.map +1 -1
- package/package.json +10 -7
package/dist/index.esm.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { createApiRef,
|
|
2
|
-
import React, { useMemo, useContext, useState, useCallback, useEffect, useRef } from 'react';
|
|
3
|
-
import { makeStyles, TextField, Chip, FormControl, FormLabel, FormControlLabel, Checkbox, InputLabel, Select, MenuItem, Button,
|
|
4
|
-
import
|
|
1
|
+
import { createApiRef, AnalyticsContext, useApi, useAnalytics, configApiRef } from '@backstage/core-plugin-api';
|
|
2
|
+
import React, { useMemo, useContext, useState, useCallback, useEffect, forwardRef, useRef } from 'react';
|
|
3
|
+
import { makeStyles, InputBase, InputAdornment, IconButton, CircularProgress, ListItemIcon, ListItemText, TextField, Chip, FormControl, FormLabel, FormControlLabel, Checkbox, InputLabel, Select, MenuItem, Button, ListItem, Box, Divider, List, Typography, ListSubheader, Menu } from '@material-ui/core';
|
|
4
|
+
import useDebounce from 'react-use/lib/useDebounce';
|
|
5
|
+
import SearchIcon from '@material-ui/icons/Search';
|
|
6
|
+
import ClearButton from '@material-ui/icons/Clear';
|
|
7
|
+
import { isEqual } from 'lodash';
|
|
5
8
|
import useAsync from 'react-use/lib/useAsync';
|
|
6
9
|
import usePrevious from 'react-use/lib/usePrevious';
|
|
10
|
+
import { createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
|
|
7
11
|
import { Autocomplete } from '@material-ui/lab';
|
|
8
12
|
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
|
9
|
-
import useDebounce from 'react-use/lib/useDebounce';
|
|
10
13
|
import { Progress, ResponseErrorPanel, EmptyState, Link } from '@backstage/core-components';
|
|
11
14
|
import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
|
|
12
15
|
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
16
|
+
import qs from 'qs';
|
|
17
|
+
import AddIcon from '@material-ui/icons/Add';
|
|
15
18
|
|
|
16
19
|
const searchApiRef = createApiRef({
|
|
17
20
|
id: "plugin.search.queryservice"
|
|
@@ -25,7 +28,7 @@ class MockSearchApi {
|
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
const useStyles$
|
|
31
|
+
const useStyles$3 = makeStyles(
|
|
29
32
|
() => ({
|
|
30
33
|
highlight: {}
|
|
31
34
|
}),
|
|
@@ -36,7 +39,7 @@ const HighlightedSearchResultText = ({
|
|
|
36
39
|
preTag,
|
|
37
40
|
postTag
|
|
38
41
|
}) => {
|
|
39
|
-
const classes = useStyles$
|
|
42
|
+
const classes = useStyles$3();
|
|
40
43
|
const terms = useMemo(
|
|
41
44
|
() => text.split(new RegExp(`(${preTag}.+?${postTag})`)),
|
|
42
45
|
[postTag, preTag, text]
|
|
@@ -71,17 +74,17 @@ const searchInitialState = {
|
|
|
71
74
|
filters: {},
|
|
72
75
|
types: []
|
|
73
76
|
};
|
|
74
|
-
const
|
|
77
|
+
const useSearchContextValue = (initialValue = searchInitialState) => {
|
|
75
78
|
var _a, _b, _c, _d;
|
|
76
|
-
const { initialState = searchInitialState, children } = props;
|
|
77
79
|
const searchApi = useApi(searchApiRef);
|
|
80
|
+
const [term, setTerm] = useState(initialValue.term);
|
|
81
|
+
const [types, setTypes] = useState(initialValue.types);
|
|
82
|
+
const [filters, setFilters] = useState(initialValue.filters);
|
|
78
83
|
const [pageCursor, setPageCursor] = useState(
|
|
79
|
-
|
|
84
|
+
initialValue.pageCursor
|
|
80
85
|
);
|
|
81
|
-
const [filters, setFilters] = useState(initialState.filters);
|
|
82
|
-
const [term, setTerm] = useState(initialState.term);
|
|
83
|
-
const [types, setTypes] = useState(initialState.types);
|
|
84
86
|
const prevTerm = usePrevious(term);
|
|
87
|
+
const prevFilters = usePrevious(filters);
|
|
85
88
|
const result = useAsync(
|
|
86
89
|
() => searchApi.query({
|
|
87
90
|
term,
|
|
@@ -89,7 +92,7 @@ const SearchContextProvider = (props) => {
|
|
|
89
92
|
pageCursor,
|
|
90
93
|
types
|
|
91
94
|
}),
|
|
92
|
-
[term,
|
|
95
|
+
[term, types, filters, pageCursor]
|
|
93
96
|
);
|
|
94
97
|
const hasNextPage = !result.loading && !result.error && ((_a = result.value) == null ? void 0 : _a.nextPageCursor);
|
|
95
98
|
const hasPreviousPage = !result.loading && !result.error && ((_b = result.value) == null ? void 0 : _b.previousPageCursor);
|
|
@@ -106,6 +109,11 @@ const SearchContextProvider = (props) => {
|
|
|
106
109
|
setPageCursor(void 0);
|
|
107
110
|
}
|
|
108
111
|
}, [term, prevTerm, setPageCursor]);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (prevFilters !== void 0 && !isEqual(filters, prevFilters)) {
|
|
114
|
+
setPageCursor(void 0);
|
|
115
|
+
}
|
|
116
|
+
}, [filters, prevFilters, setPageCursor]);
|
|
109
117
|
const value = {
|
|
110
118
|
result,
|
|
111
119
|
filters,
|
|
@@ -119,14 +127,248 @@ const SearchContextProvider = (props) => {
|
|
|
119
127
|
fetchNextPage: hasNextPage ? fetchNextPage : void 0,
|
|
120
128
|
fetchPreviousPage: hasPreviousPage ? fetchPreviousPage : void 0
|
|
121
129
|
};
|
|
122
|
-
|
|
130
|
+
return value;
|
|
131
|
+
};
|
|
132
|
+
const LocalSearchContext = (props) => {
|
|
133
|
+
const { initialState, children } = props;
|
|
134
|
+
const value = useSearchContextValue(initialState);
|
|
123
135
|
return /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
124
|
-
attributes: { searchTypes: types.sort().join(",") }
|
|
136
|
+
attributes: { searchTypes: value.types.sort().join(",") }
|
|
125
137
|
}, /* @__PURE__ */ React.createElement(SearchContext.Provider, {
|
|
126
|
-
value:
|
|
127
|
-
|
|
138
|
+
value: createVersionedValueMap({ 1: value })
|
|
139
|
+
}, children));
|
|
140
|
+
};
|
|
141
|
+
const SearchContextProvider = (props) => {
|
|
142
|
+
const { initialState, inheritParentContextIfAvailable, children } = props;
|
|
143
|
+
const hasParentContext = useSearchContextCheck();
|
|
144
|
+
return hasParentContext && inheritParentContextIfAvailable ? /* @__PURE__ */ React.createElement(React.Fragment, null, children) : /* @__PURE__ */ React.createElement(LocalSearchContext, {
|
|
145
|
+
initialState
|
|
146
|
+
}, children);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const TrackSearch = ({ children }) => {
|
|
150
|
+
const analytics = useAnalytics();
|
|
151
|
+
const { term } = useSearch();
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (term) {
|
|
154
|
+
analytics.captureEvent("search", term);
|
|
155
|
+
}
|
|
156
|
+
}, [analytics, term]);
|
|
157
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
function withContext$1(Component) {
|
|
161
|
+
return forwardRef((props, ref) => /* @__PURE__ */ React.createElement(SearchContextProvider, {
|
|
162
|
+
inheritParentContextIfAvailable: true
|
|
163
|
+
}, /* @__PURE__ */ React.createElement(Component, {
|
|
164
|
+
...props,
|
|
165
|
+
ref
|
|
166
|
+
})));
|
|
167
|
+
}
|
|
168
|
+
const SearchBarBase = withContext$1(
|
|
169
|
+
forwardRef((props, ref) => {
|
|
170
|
+
const {
|
|
171
|
+
onChange,
|
|
172
|
+
onKeyDown = () => {
|
|
173
|
+
},
|
|
174
|
+
onClear = () => {
|
|
175
|
+
},
|
|
176
|
+
onSubmit = () => {
|
|
177
|
+
},
|
|
178
|
+
debounceTime = 200,
|
|
179
|
+
clearButton = true,
|
|
180
|
+
fullWidth = true,
|
|
181
|
+
value: defaultValue,
|
|
182
|
+
placeholder: defaultPlaceholder,
|
|
183
|
+
inputProps: defaultInputProps = {},
|
|
184
|
+
endAdornment: defaultEndAdornment,
|
|
185
|
+
...rest
|
|
186
|
+
} = props;
|
|
187
|
+
const configApi = useApi(configApiRef);
|
|
188
|
+
const [value, setValue] = useState("");
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
setValue(
|
|
191
|
+
(prevValue) => prevValue !== defaultValue ? String(defaultValue) : prevValue
|
|
192
|
+
);
|
|
193
|
+
}, [defaultValue]);
|
|
194
|
+
useDebounce(() => onChange(value), debounceTime, [value]);
|
|
195
|
+
const handleChange = useCallback(
|
|
196
|
+
(e) => {
|
|
197
|
+
setValue(e.target.value);
|
|
198
|
+
},
|
|
199
|
+
[setValue]
|
|
200
|
+
);
|
|
201
|
+
const handleKeyDown = useCallback(
|
|
202
|
+
(e) => {
|
|
203
|
+
if (onKeyDown)
|
|
204
|
+
onKeyDown(e);
|
|
205
|
+
if (onSubmit && e.key === "Enter") {
|
|
206
|
+
onSubmit();
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
[onKeyDown, onSubmit]
|
|
210
|
+
);
|
|
211
|
+
const handleClear = useCallback(() => {
|
|
212
|
+
onChange("");
|
|
213
|
+
if (onClear) {
|
|
214
|
+
onClear();
|
|
215
|
+
}
|
|
216
|
+
}, [onChange, onClear]);
|
|
217
|
+
const placeholder = defaultPlaceholder != null ? defaultPlaceholder : `Search in ${configApi.getOptionalString("app.title") || "Backstage"}`;
|
|
218
|
+
const startAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
|
|
219
|
+
position: "start"
|
|
220
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
221
|
+
"aria-label": "Query",
|
|
222
|
+
size: "small",
|
|
223
|
+
disabled: true
|
|
224
|
+
}, /* @__PURE__ */ React.createElement(SearchIcon, null)));
|
|
225
|
+
const endAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
|
|
226
|
+
position: "end"
|
|
227
|
+
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
228
|
+
"aria-label": "Clear",
|
|
229
|
+
size: "small",
|
|
230
|
+
onClick: handleClear
|
|
231
|
+
}, /* @__PURE__ */ React.createElement(ClearButton, null)));
|
|
232
|
+
return /* @__PURE__ */ React.createElement(TrackSearch, null, /* @__PURE__ */ React.createElement(InputBase, {
|
|
233
|
+
"data-testid": "search-bar-next",
|
|
234
|
+
ref,
|
|
235
|
+
value,
|
|
236
|
+
placeholder,
|
|
237
|
+
startAdornment,
|
|
238
|
+
endAdornment: clearButton ? endAdornment : defaultEndAdornment,
|
|
239
|
+
inputProps: { "aria-label": "Search", ...defaultInputProps },
|
|
240
|
+
fullWidth,
|
|
241
|
+
onChange: handleChange,
|
|
242
|
+
onKeyDown: handleKeyDown,
|
|
243
|
+
...rest
|
|
244
|
+
}));
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
const SearchBar = withContext$1(
|
|
248
|
+
forwardRef((props, ref) => {
|
|
249
|
+
const { value: initialValue = "", onChange, ...rest } = props;
|
|
250
|
+
const { term, setTerm } = useSearch();
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
if (initialValue) {
|
|
253
|
+
setTerm(String(initialValue));
|
|
254
|
+
}
|
|
255
|
+
}, [initialValue, setTerm]);
|
|
256
|
+
const handleChange = useCallback(
|
|
257
|
+
(newValue) => {
|
|
258
|
+
if (onChange) {
|
|
259
|
+
onChange(newValue);
|
|
260
|
+
} else {
|
|
261
|
+
setTerm(newValue);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
[onChange, setTerm]
|
|
265
|
+
);
|
|
266
|
+
return /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
267
|
+
attributes: { pluginId: "search", extension: "SearchBar" }
|
|
268
|
+
}, /* @__PURE__ */ React.createElement(SearchBarBase, {
|
|
269
|
+
...rest,
|
|
270
|
+
ref,
|
|
271
|
+
value: term,
|
|
272
|
+
onChange: handleChange
|
|
273
|
+
}));
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const withContext = (Component) => {
|
|
278
|
+
return (props) => /* @__PURE__ */ React.createElement(SearchContextProvider, {
|
|
279
|
+
inheritParentContextIfAvailable: true
|
|
280
|
+
}, /* @__PURE__ */ React.createElement(Component, {
|
|
281
|
+
...props
|
|
128
282
|
}));
|
|
129
283
|
};
|
|
284
|
+
const SearchAutocomplete = withContext(
|
|
285
|
+
function SearchAutocompleteComponent(props) {
|
|
286
|
+
const {
|
|
287
|
+
loading,
|
|
288
|
+
value,
|
|
289
|
+
onChange = () => {
|
|
290
|
+
},
|
|
291
|
+
options = [],
|
|
292
|
+
getOptionLabel = (option) => String(option),
|
|
293
|
+
inputPlaceholder,
|
|
294
|
+
inputDebounceTime,
|
|
295
|
+
freeSolo = true,
|
|
296
|
+
fullWidth = true,
|
|
297
|
+
clearOnBlur = false,
|
|
298
|
+
"data-testid": dataTestId = "search-autocomplete",
|
|
299
|
+
...rest
|
|
300
|
+
} = props;
|
|
301
|
+
const { setTerm } = useSearch();
|
|
302
|
+
const getInputValue = useCallback(
|
|
303
|
+
(option) => {
|
|
304
|
+
if (!option)
|
|
305
|
+
return "";
|
|
306
|
+
if (typeof option === "string")
|
|
307
|
+
return option;
|
|
308
|
+
return getOptionLabel(option);
|
|
309
|
+
},
|
|
310
|
+
[getOptionLabel]
|
|
311
|
+
);
|
|
312
|
+
const inputValue = useMemo(
|
|
313
|
+
() => getInputValue(value),
|
|
314
|
+
[value, getInputValue]
|
|
315
|
+
);
|
|
316
|
+
const handleChange = useCallback(
|
|
317
|
+
(event, option, reason, details) => {
|
|
318
|
+
setTerm(getInputValue(option));
|
|
319
|
+
onChange(event, option, reason, details);
|
|
320
|
+
},
|
|
321
|
+
[getInputValue, setTerm, onChange]
|
|
322
|
+
);
|
|
323
|
+
const renderInput = useCallback(
|
|
324
|
+
({
|
|
325
|
+
InputProps: { ref, endAdornment },
|
|
326
|
+
InputLabelProps,
|
|
327
|
+
...params
|
|
328
|
+
}) => /* @__PURE__ */ React.createElement(SearchBar, {
|
|
329
|
+
...params,
|
|
330
|
+
ref,
|
|
331
|
+
clearButton: false,
|
|
332
|
+
value: inputValue,
|
|
333
|
+
placeholder: inputPlaceholder,
|
|
334
|
+
debounceTime: inputDebounceTime,
|
|
335
|
+
endAdornment: loading ? /* @__PURE__ */ React.createElement(CircularProgress, {
|
|
336
|
+
"data-testid": "search-autocomplete-progressbar",
|
|
337
|
+
color: "inherit",
|
|
338
|
+
size: 20
|
|
339
|
+
}) : endAdornment
|
|
340
|
+
}),
|
|
341
|
+
[loading, inputValue, inputPlaceholder, inputDebounceTime]
|
|
342
|
+
);
|
|
343
|
+
return /* @__PURE__ */ React.createElement(Autocomplete, {
|
|
344
|
+
...rest,
|
|
345
|
+
"data-testid": dataTestId,
|
|
346
|
+
value,
|
|
347
|
+
onChange: handleChange,
|
|
348
|
+
options,
|
|
349
|
+
getOptionLabel,
|
|
350
|
+
renderInput,
|
|
351
|
+
freeSolo,
|
|
352
|
+
fullWidth,
|
|
353
|
+
clearOnBlur
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const SearchAutocompleteDefaultOption = ({
|
|
359
|
+
icon,
|
|
360
|
+
primaryText,
|
|
361
|
+
primaryTextTypographyProps,
|
|
362
|
+
secondaryText,
|
|
363
|
+
secondaryTextTypographyProps,
|
|
364
|
+
disableTextTypography
|
|
365
|
+
}) => /* @__PURE__ */ React.createElement(React.Fragment, null, icon ? /* @__PURE__ */ React.createElement(ListItemIcon, null, icon) : null, /* @__PURE__ */ React.createElement(ListItemText, {
|
|
366
|
+
primary: primaryText,
|
|
367
|
+
primaryTypographyProps: primaryTextTypographyProps,
|
|
368
|
+
secondary: secondaryText,
|
|
369
|
+
secondaryTypographyProps: secondaryTextTypographyProps,
|
|
370
|
+
disableTypography: disableTextTypography
|
|
371
|
+
}));
|
|
130
372
|
|
|
131
373
|
const useAsyncFilterValues = (fn, inputValue, defaultValues = [], debounce = 250) => {
|
|
132
374
|
const valuesMemo = useRef({});
|
|
@@ -234,7 +476,7 @@ const AutocompleteFilter = (props) => {
|
|
|
234
476
|
});
|
|
235
477
|
};
|
|
236
478
|
|
|
237
|
-
const useStyles$
|
|
479
|
+
const useStyles$2 = makeStyles({
|
|
238
480
|
label: {
|
|
239
481
|
textTransform: "capitalize"
|
|
240
482
|
}
|
|
@@ -248,7 +490,7 @@ const CheckboxFilter = (props) => {
|
|
|
248
490
|
values: givenValues = [],
|
|
249
491
|
valuesDebounceMs
|
|
250
492
|
} = props;
|
|
251
|
-
const classes = useStyles$
|
|
493
|
+
const classes = useStyles$2();
|
|
252
494
|
const { filters, setFilters } = useSearch();
|
|
253
495
|
useDefaultFilterValue(name, defaultValue);
|
|
254
496
|
const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
|
|
@@ -303,7 +545,7 @@ const SelectFilter = (props) => {
|
|
|
303
545
|
values: givenValues,
|
|
304
546
|
valuesDebounceMs
|
|
305
547
|
} = props;
|
|
306
|
-
const classes = useStyles$
|
|
548
|
+
const classes = useStyles$2();
|
|
307
549
|
useDefaultFilterValue(name, defaultValue);
|
|
308
550
|
const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
|
|
309
551
|
const defaultValues = typeof givenValues === "function" ? void 0 : givenValues;
|
|
@@ -362,39 +604,68 @@ SearchFilter.Autocomplete = (props) => /* @__PURE__ */ React.createElement(Searc
|
|
|
362
604
|
component: AutocompleteFilter
|
|
363
605
|
});
|
|
364
606
|
|
|
365
|
-
const
|
|
366
|
-
const {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
371
|
-
}
|
|
372
|
-
if (error) {
|
|
373
|
-
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
|
|
374
|
-
title: "Error encountered while fetching search results",
|
|
375
|
-
error
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
if (!(value == null ? void 0 : value.results.length)) {
|
|
379
|
-
return /* @__PURE__ */ React.createElement(EmptyState, {
|
|
380
|
-
missing: "data",
|
|
381
|
-
title: "Sorry, no results were found"
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, children({ results: value.results }));
|
|
607
|
+
const SearchResultContext = (props) => {
|
|
608
|
+
const { children } = props;
|
|
609
|
+
const context = useSearch();
|
|
610
|
+
const state = context.result;
|
|
611
|
+
return children(state);
|
|
385
612
|
};
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
613
|
+
const SearchResultApi = (props) => {
|
|
614
|
+
const { query, children } = props;
|
|
615
|
+
const searchApi = useApi(searchApiRef);
|
|
616
|
+
const state = useAsync(
|
|
617
|
+
() => {
|
|
618
|
+
var _a, _b, _c;
|
|
619
|
+
return searchApi.query({
|
|
620
|
+
term: (_a = query.term) != null ? _a : "",
|
|
621
|
+
types: (_b = query.types) != null ? _b : [],
|
|
622
|
+
filters: (_c = query.filters) != null ? _c : {},
|
|
623
|
+
pageCursor: query.pageCursor
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
[query]
|
|
627
|
+
);
|
|
628
|
+
return children(state);
|
|
629
|
+
};
|
|
630
|
+
const SearchResultState = (props) => {
|
|
631
|
+
const { query, children } = props;
|
|
632
|
+
return query ? /* @__PURE__ */ React.createElement(SearchResultApi, {
|
|
633
|
+
query
|
|
634
|
+
}, children) : /* @__PURE__ */ React.createElement(SearchResultContext, null, children);
|
|
635
|
+
};
|
|
636
|
+
const SearchResultComponent = (props) => {
|
|
637
|
+
const { query, children } = props;
|
|
638
|
+
return /* @__PURE__ */ React.createElement(SearchResultState, {
|
|
639
|
+
query
|
|
640
|
+
}, ({ loading, error, value }) => {
|
|
641
|
+
if (loading) {
|
|
642
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
391
643
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
644
|
+
if (error) {
|
|
645
|
+
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
|
|
646
|
+
title: "Error encountered while fetching search results",
|
|
647
|
+
error
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
if (!(value == null ? void 0 : value.results.length)) {
|
|
651
|
+
return /* @__PURE__ */ React.createElement(EmptyState, {
|
|
652
|
+
missing: "data",
|
|
653
|
+
title: "Sorry, no results were found"
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return children(value);
|
|
657
|
+
});
|
|
395
658
|
};
|
|
659
|
+
const SearchResult = (props) => /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
660
|
+
attributes: {
|
|
661
|
+
pluginId: "search",
|
|
662
|
+
extension: "SearchResult"
|
|
663
|
+
}
|
|
664
|
+
}, /* @__PURE__ */ React.createElement(SearchResultComponent, {
|
|
665
|
+
...props
|
|
666
|
+
}));
|
|
396
667
|
|
|
397
|
-
const useStyles = makeStyles((theme) => ({
|
|
668
|
+
const useStyles$1 = makeStyles((theme) => ({
|
|
398
669
|
root: {
|
|
399
670
|
display: "flex",
|
|
400
671
|
justifyContent: "space-between",
|
|
@@ -404,12 +675,12 @@ const useStyles = makeStyles((theme) => ({
|
|
|
404
675
|
}));
|
|
405
676
|
const SearchResultPager = () => {
|
|
406
677
|
const { fetchNextPage, fetchPreviousPage } = useSearch();
|
|
407
|
-
const classes = useStyles();
|
|
678
|
+
const classes = useStyles$1();
|
|
408
679
|
if (!fetchNextPage && !fetchPreviousPage) {
|
|
409
680
|
return /* @__PURE__ */ React.createElement(React.Fragment, null);
|
|
410
681
|
}
|
|
411
682
|
return /* @__PURE__ */ React.createElement("nav", {
|
|
412
|
-
"
|
|
683
|
+
"aria-label": "pagination navigation",
|
|
413
684
|
className: classes.root
|
|
414
685
|
}, /* @__PURE__ */ React.createElement(Button, {
|
|
415
686
|
"aria-label": "previous page",
|
|
@@ -424,105 +695,6 @@ const SearchResultPager = () => {
|
|
|
424
695
|
}, "Next"));
|
|
425
696
|
};
|
|
426
697
|
|
|
427
|
-
const TrackSearch = ({ children }) => {
|
|
428
|
-
const analytics = useAnalytics();
|
|
429
|
-
const { term } = useSearch();
|
|
430
|
-
useEffect(() => {
|
|
431
|
-
if (term) {
|
|
432
|
-
analytics.captureEvent("search", term);
|
|
433
|
-
}
|
|
434
|
-
}, [analytics, term]);
|
|
435
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const SearchBarBase = ({
|
|
439
|
-
onChange,
|
|
440
|
-
onKeyDown,
|
|
441
|
-
onSubmit,
|
|
442
|
-
debounceTime = 200,
|
|
443
|
-
clearButton = true,
|
|
444
|
-
fullWidth = true,
|
|
445
|
-
value: defaultValue,
|
|
446
|
-
inputProps: defaultInputProps = {},
|
|
447
|
-
endAdornment: defaultEndAdornment,
|
|
448
|
-
...props
|
|
449
|
-
}) => {
|
|
450
|
-
const configApi = useApi(configApiRef);
|
|
451
|
-
const [value, setValue] = useState(defaultValue);
|
|
452
|
-
const hasSearchContext = useSearchContextCheck();
|
|
453
|
-
useEffect(() => {
|
|
454
|
-
setValue(
|
|
455
|
-
(prevValue) => prevValue !== defaultValue ? defaultValue : prevValue
|
|
456
|
-
);
|
|
457
|
-
}, [defaultValue]);
|
|
458
|
-
useDebounce(() => onChange(value), debounceTime, [value]);
|
|
459
|
-
const handleChange = useCallback(
|
|
460
|
-
(e) => {
|
|
461
|
-
setValue(e.target.value);
|
|
462
|
-
},
|
|
463
|
-
[setValue]
|
|
464
|
-
);
|
|
465
|
-
const handleKeyDown = useCallback(
|
|
466
|
-
(e) => {
|
|
467
|
-
if (onKeyDown)
|
|
468
|
-
onKeyDown(e);
|
|
469
|
-
if (onSubmit && e.key === "Enter") {
|
|
470
|
-
onSubmit();
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
[onKeyDown, onSubmit]
|
|
474
|
-
);
|
|
475
|
-
const handleClear = useCallback(() => {
|
|
476
|
-
onChange("");
|
|
477
|
-
}, [onChange]);
|
|
478
|
-
const placeholder = `Search in ${configApi.getOptionalString("app.title") || "Backstage"}`;
|
|
479
|
-
const startAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
|
|
480
|
-
position: "start"
|
|
481
|
-
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
482
|
-
"aria-label": "Query",
|
|
483
|
-
disabled: true
|
|
484
|
-
}, /* @__PURE__ */ React.createElement(SearchIcon, null)));
|
|
485
|
-
const endAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
|
|
486
|
-
position: "end"
|
|
487
|
-
}, /* @__PURE__ */ React.createElement(IconButton, {
|
|
488
|
-
"aria-label": "Clear",
|
|
489
|
-
onClick: handleClear
|
|
490
|
-
}, /* @__PURE__ */ React.createElement(ClearButton, null)));
|
|
491
|
-
const searchBar = /* @__PURE__ */ React.createElement(TrackSearch, null, /* @__PURE__ */ React.createElement(InputBase, {
|
|
492
|
-
"data-testid": "search-bar-next",
|
|
493
|
-
value,
|
|
494
|
-
placeholder,
|
|
495
|
-
startAdornment,
|
|
496
|
-
endAdornment: clearButton ? endAdornment : defaultEndAdornment,
|
|
497
|
-
inputProps: { "aria-label": "Search", ...defaultInputProps },
|
|
498
|
-
fullWidth,
|
|
499
|
-
onChange: handleChange,
|
|
500
|
-
onKeyDown: handleKeyDown,
|
|
501
|
-
...props
|
|
502
|
-
}));
|
|
503
|
-
return hasSearchContext ? searchBar : /* @__PURE__ */ React.createElement(SearchContextProvider, null, searchBar);
|
|
504
|
-
};
|
|
505
|
-
const SearchBar = ({ onChange, ...props }) => {
|
|
506
|
-
const { term, setTerm } = useSearch();
|
|
507
|
-
const handleChange = useCallback(
|
|
508
|
-
(newValue) => {
|
|
509
|
-
if (onChange) {
|
|
510
|
-
onChange(newValue);
|
|
511
|
-
} else {
|
|
512
|
-
setTerm(newValue);
|
|
513
|
-
}
|
|
514
|
-
},
|
|
515
|
-
[onChange, setTerm]
|
|
516
|
-
);
|
|
517
|
-
return /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
518
|
-
attributes: { pluginId: "search", extension: "SearchBar" }
|
|
519
|
-
}, /* @__PURE__ */ React.createElement(SearchBarBase, {
|
|
520
|
-
value: term,
|
|
521
|
-
onChange: handleChange,
|
|
522
|
-
...props
|
|
523
|
-
}));
|
|
524
|
-
};
|
|
525
|
-
|
|
526
698
|
const DefaultResultListItemComponent = ({
|
|
527
699
|
result,
|
|
528
700
|
highlight,
|
|
@@ -578,5 +750,277 @@ const HigherOrderDefaultResultListItem = (props) => {
|
|
|
578
750
|
}));
|
|
579
751
|
};
|
|
580
752
|
|
|
581
|
-
|
|
753
|
+
const SearchResultListLayout = (props) => {
|
|
754
|
+
const { loading, error, resultItems, renderResultItem, ...rest } = props;
|
|
755
|
+
return /* @__PURE__ */ React.createElement(List, {
|
|
756
|
+
...rest
|
|
757
|
+
}, loading ? /* @__PURE__ */ React.createElement(Progress, null) : null, !loading && error ? /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
|
|
758
|
+
title: "Error encountered while fetching search results",
|
|
759
|
+
error
|
|
760
|
+
}) : null, !loading && !error && (resultItems == null ? void 0 : resultItems.length) ? resultItems.map((resultItem) => {
|
|
761
|
+
var _a;
|
|
762
|
+
return (_a = renderResultItem == null ? void 0 : renderResultItem(resultItem)) != null ? _a : null;
|
|
763
|
+
}) : null, !loading && !error && !(resultItems == null ? void 0 : resultItems.length) ? /* @__PURE__ */ React.createElement(EmptyState, {
|
|
764
|
+
missing: "data",
|
|
765
|
+
title: "Sorry, no results were found"
|
|
766
|
+
}) : null);
|
|
767
|
+
};
|
|
768
|
+
const SearchResultList = (props) => {
|
|
769
|
+
const {
|
|
770
|
+
query,
|
|
771
|
+
renderResultItem = ({ document }) => /* @__PURE__ */ React.createElement(HigherOrderDefaultResultListItem, {
|
|
772
|
+
key: document.location,
|
|
773
|
+
result: document
|
|
774
|
+
}),
|
|
775
|
+
...rest
|
|
776
|
+
} = props;
|
|
777
|
+
return /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
778
|
+
attributes: {
|
|
779
|
+
pluginId: "search",
|
|
780
|
+
extension: "SearchResultList"
|
|
781
|
+
}
|
|
782
|
+
}, /* @__PURE__ */ React.createElement(SearchResultState, {
|
|
783
|
+
query
|
|
784
|
+
}, ({ loading, error, value }) => /* @__PURE__ */ React.createElement(SearchResultListLayout, {
|
|
785
|
+
...rest,
|
|
786
|
+
loading,
|
|
787
|
+
error,
|
|
788
|
+
resultItems: value == null ? void 0 : value.results,
|
|
789
|
+
renderResultItem
|
|
790
|
+
})));
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const useStyles = makeStyles((theme) => ({
|
|
794
|
+
listSubheader: {
|
|
795
|
+
display: "flex",
|
|
796
|
+
alignItems: "center"
|
|
797
|
+
},
|
|
798
|
+
listSubheaderName: {
|
|
799
|
+
marginLeft: theme.spacing(1),
|
|
800
|
+
textTransform: "uppercase"
|
|
801
|
+
},
|
|
802
|
+
listSubheaderChip: {
|
|
803
|
+
color: theme.palette.text.secondary,
|
|
804
|
+
margin: theme.spacing(0, 0, 0, 1.5)
|
|
805
|
+
},
|
|
806
|
+
listSubheaderFilter: {
|
|
807
|
+
display: "flex",
|
|
808
|
+
color: theme.palette.text.secondary,
|
|
809
|
+
margin: theme.spacing(0, 0, 0, 1.5)
|
|
810
|
+
},
|
|
811
|
+
listSubheaderLink: {
|
|
812
|
+
marginLeft: "auto",
|
|
813
|
+
display: "flex",
|
|
814
|
+
alignItems: "center"
|
|
815
|
+
},
|
|
816
|
+
listSubheaderLinkIcon: {
|
|
817
|
+
fontSize: "inherit",
|
|
818
|
+
marginLeft: theme.spacing(0.5)
|
|
819
|
+
}
|
|
820
|
+
}));
|
|
821
|
+
const SearchResultGroupFilterFieldLayout = (props) => {
|
|
822
|
+
const classes = useStyles();
|
|
823
|
+
const { label, children, ...rest } = props;
|
|
824
|
+
return /* @__PURE__ */ React.createElement(Chip, {
|
|
825
|
+
...rest,
|
|
826
|
+
className: classes.listSubheaderFilter,
|
|
827
|
+
variant: "outlined",
|
|
828
|
+
label: /* @__PURE__ */ React.createElement(React.Fragment, null, label, ": ", children)
|
|
829
|
+
});
|
|
830
|
+
};
|
|
831
|
+
const NullIcon = () => null;
|
|
832
|
+
const useSearchResultGroupTextFilterStyles = makeStyles((theme) => ({
|
|
833
|
+
root: {
|
|
834
|
+
fontSize: "inherit",
|
|
835
|
+
"&:focus": {
|
|
836
|
+
outline: "none",
|
|
837
|
+
background: theme.palette.common.white
|
|
838
|
+
},
|
|
839
|
+
"&:not(:focus)": {
|
|
840
|
+
cursor: "pointer",
|
|
841
|
+
color: theme.palette.primary.main,
|
|
842
|
+
"&:hover": {
|
|
843
|
+
textDecoration: "underline"
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}));
|
|
848
|
+
const SearchResultGroupTextFilterField = (props) => {
|
|
849
|
+
const classes = useSearchResultGroupTextFilterStyles();
|
|
850
|
+
const { label, value = "None", onChange, onDelete } = props;
|
|
851
|
+
const handleChange = useCallback(
|
|
852
|
+
(e) => {
|
|
853
|
+
onChange(e.target.value);
|
|
854
|
+
},
|
|
855
|
+
[onChange]
|
|
856
|
+
);
|
|
857
|
+
return /* @__PURE__ */ React.createElement(SearchResultGroupFilterFieldLayout, {
|
|
858
|
+
label,
|
|
859
|
+
onDelete
|
|
860
|
+
}, /* @__PURE__ */ React.createElement(Typography, {
|
|
861
|
+
role: "textbox",
|
|
862
|
+
component: "span",
|
|
863
|
+
className: classes.root,
|
|
864
|
+
onChange: handleChange,
|
|
865
|
+
contentEditable: true,
|
|
866
|
+
suppressContentEditableWarning: true
|
|
867
|
+
}, value));
|
|
868
|
+
};
|
|
869
|
+
const useSearchResultGroupSelectFilterStyles = makeStyles((theme) => ({
|
|
870
|
+
root: {
|
|
871
|
+
fontSize: "inherit",
|
|
872
|
+
"&:not(:focus)": {
|
|
873
|
+
cursor: "pointer",
|
|
874
|
+
color: theme.palette.primary.main,
|
|
875
|
+
"&:hover": {
|
|
876
|
+
textDecoration: "underline"
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
"&:focus": {
|
|
880
|
+
outline: "none"
|
|
881
|
+
},
|
|
882
|
+
"&>div:first-child": {
|
|
883
|
+
padding: 0
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}));
|
|
887
|
+
const SearchResultGroupSelectFilterField = (props) => {
|
|
888
|
+
const classes = useSearchResultGroupSelectFilterStyles();
|
|
889
|
+
const { label, value = "none", onChange, onDelete, children } = props;
|
|
890
|
+
const handleChange = useCallback(
|
|
891
|
+
(e) => {
|
|
892
|
+
onChange(e.target.value);
|
|
893
|
+
},
|
|
894
|
+
[onChange]
|
|
895
|
+
);
|
|
896
|
+
return /* @__PURE__ */ React.createElement(SearchResultGroupFilterFieldLayout, {
|
|
897
|
+
label,
|
|
898
|
+
onDelete
|
|
899
|
+
}, /* @__PURE__ */ React.createElement(Select, {
|
|
900
|
+
className: classes.root,
|
|
901
|
+
value,
|
|
902
|
+
onChange: handleChange,
|
|
903
|
+
input: /* @__PURE__ */ React.createElement(InputBase, null),
|
|
904
|
+
IconComponent: NullIcon
|
|
905
|
+
}, /* @__PURE__ */ React.createElement(MenuItem, {
|
|
906
|
+
value: "none"
|
|
907
|
+
}, "None"), children));
|
|
908
|
+
};
|
|
909
|
+
function SearchResultGroupLayout(props) {
|
|
910
|
+
const classes = useStyles();
|
|
911
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
912
|
+
const {
|
|
913
|
+
loading,
|
|
914
|
+
error,
|
|
915
|
+
icon,
|
|
916
|
+
title,
|
|
917
|
+
titleProps = {},
|
|
918
|
+
link,
|
|
919
|
+
linkProps = {},
|
|
920
|
+
filterOptions,
|
|
921
|
+
renderFilterOption,
|
|
922
|
+
filterFields,
|
|
923
|
+
renderFilterField,
|
|
924
|
+
resultItems,
|
|
925
|
+
renderResultItem,
|
|
926
|
+
...rest
|
|
927
|
+
} = props;
|
|
928
|
+
const handleClick = useCallback((e) => {
|
|
929
|
+
setAnchorEl(e.currentTarget);
|
|
930
|
+
}, []);
|
|
931
|
+
const handleClose = useCallback(() => {
|
|
932
|
+
setAnchorEl(null);
|
|
933
|
+
}, []);
|
|
934
|
+
return /* @__PURE__ */ React.createElement(List, {
|
|
935
|
+
...rest
|
|
936
|
+
}, /* @__PURE__ */ React.createElement(ListSubheader, {
|
|
937
|
+
className: classes.listSubheader
|
|
938
|
+
}, icon, /* @__PURE__ */ React.createElement(Typography, {
|
|
939
|
+
className: classes.listSubheaderName,
|
|
940
|
+
component: "strong",
|
|
941
|
+
...titleProps
|
|
942
|
+
}, title), filterOptions ? /* @__PURE__ */ React.createElement(Chip, {
|
|
943
|
+
className: classes.listSubheaderChip,
|
|
944
|
+
component: "button",
|
|
945
|
+
icon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
946
|
+
variant: "outlined",
|
|
947
|
+
label: "Add filter",
|
|
948
|
+
"aria-controls": "filters-menu",
|
|
949
|
+
"aria-haspopup": "true",
|
|
950
|
+
onClick: handleClick
|
|
951
|
+
}) : null, filterOptions ? /* @__PURE__ */ React.createElement(Menu, {
|
|
952
|
+
id: "filters-menu",
|
|
953
|
+
anchorEl,
|
|
954
|
+
open: Boolean(anchorEl),
|
|
955
|
+
onClose: handleClose,
|
|
956
|
+
onClick: handleClose,
|
|
957
|
+
keepMounted: true
|
|
958
|
+
}, filterOptions.map(
|
|
959
|
+
(filterOption) => renderFilterOption ? renderFilterOption(filterOption) : /* @__PURE__ */ React.createElement(MenuItem, {
|
|
960
|
+
key: String(filterOption),
|
|
961
|
+
value: String(filterOption)
|
|
962
|
+
}, filterOption)
|
|
963
|
+
)) : null, filterFields == null ? void 0 : filterFields.map(
|
|
964
|
+
(filterField) => {
|
|
965
|
+
var _a;
|
|
966
|
+
return (_a = renderFilterField == null ? void 0 : renderFilterField(filterField)) != null ? _a : null;
|
|
967
|
+
}
|
|
968
|
+
), /* @__PURE__ */ React.createElement(Link, {
|
|
969
|
+
className: classes.listSubheaderLink,
|
|
970
|
+
to: "/search",
|
|
971
|
+
...linkProps
|
|
972
|
+
}, link != null ? link : /* @__PURE__ */ React.createElement(React.Fragment, null, "See all", /* @__PURE__ */ React.createElement(ArrowForwardIosIcon, {
|
|
973
|
+
className: classes.listSubheaderLinkIcon
|
|
974
|
+
})))), loading ? /* @__PURE__ */ React.createElement(Progress, null) : null, !loading && error ? /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
|
|
975
|
+
title: "Error encountered while fetching search results",
|
|
976
|
+
error
|
|
977
|
+
}) : null, !loading && !error && (resultItems == null ? void 0 : resultItems.length) ? resultItems.map((resultItem) => {
|
|
978
|
+
var _a;
|
|
979
|
+
return (_a = renderResultItem == null ? void 0 : renderResultItem(resultItem)) != null ? _a : null;
|
|
980
|
+
}) : null, !loading && !error && !(resultItems == null ? void 0 : resultItems.length) ? /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(EmptyState, {
|
|
981
|
+
missing: "data",
|
|
982
|
+
title: "Sorry, no results were found"
|
|
983
|
+
})) : null);
|
|
984
|
+
}
|
|
985
|
+
function SearchResultGroup(props) {
|
|
986
|
+
const {
|
|
987
|
+
query,
|
|
988
|
+
linkProps = {},
|
|
989
|
+
renderResultItem = ({ document }) => /* @__PURE__ */ React.createElement(HigherOrderDefaultResultListItem, {
|
|
990
|
+
key: document.location,
|
|
991
|
+
result: document
|
|
992
|
+
}),
|
|
993
|
+
...rest
|
|
994
|
+
} = props;
|
|
995
|
+
const to = `/search?${qs.stringify(
|
|
996
|
+
{
|
|
997
|
+
query: query.term,
|
|
998
|
+
types: query.types,
|
|
999
|
+
filters: query.filters,
|
|
1000
|
+
pageCursor: query.pageCursor
|
|
1001
|
+
},
|
|
1002
|
+
{ arrayFormat: "brackets" }
|
|
1003
|
+
)}`;
|
|
1004
|
+
return /* @__PURE__ */ React.createElement(AnalyticsContext, {
|
|
1005
|
+
attributes: {
|
|
1006
|
+
pluginId: "search",
|
|
1007
|
+
extension: "SearchResultGroup"
|
|
1008
|
+
}
|
|
1009
|
+
}, /* @__PURE__ */ React.createElement(SearchResultState, {
|
|
1010
|
+
query
|
|
1011
|
+
}, ({ loading, error, value }) => {
|
|
1012
|
+
var _a;
|
|
1013
|
+
return /* @__PURE__ */ React.createElement(SearchResultGroupLayout, {
|
|
1014
|
+
...rest,
|
|
1015
|
+
loading,
|
|
1016
|
+
error,
|
|
1017
|
+
linkProps: { to, ...linkProps },
|
|
1018
|
+
resultItems: value == null ? void 0 : value.results,
|
|
1019
|
+
renderResultItem,
|
|
1020
|
+
filterFields: Object.keys((_a = query.filters) != null ? _a : {})
|
|
1021
|
+
});
|
|
1022
|
+
}));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export { AutocompleteFilter, CheckboxFilter, HigherOrderDefaultResultListItem as DefaultResultListItem, HighlightedSearchResultText, MockSearchApi, SearchAutocomplete, SearchAutocompleteDefaultOption, SearchBar, SearchBarBase, SearchContextProvider, SearchFilter, SearchResult, SearchResultApi, SearchResultComponent, SearchResultContext, SearchResultGroup, SearchResultGroupFilterFieldLayout, SearchResultGroupLayout, SearchResultGroupSelectFilterField, SearchResultGroupTextFilterField, SearchResultList, SearchResultListLayout, SearchResultPager, SearchResultState, SelectFilter, searchApiRef, useSearch, useSearchContextCheck };
|
|
582
1026
|
//# sourceMappingURL=index.esm.js.map
|