@backstage/plugin-search 0.5.6 → 0.6.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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @backstage/plugin-search
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2f0d3d3278: Forwarding classes to HomePageSearchBar instead of using className prop. For custom styles of the HomePageSearchBar, use classes prop instead:
8
+
9
+ ```diff
10
+ <HomePageSearchBar
11
+ - className={searchBar}
12
+ + classes={{ root: classes.searchBar }}
13
+ placeholder="Search"
14
+ />
15
+ ```
16
+
17
+ - 1dbe63ec39: The way labels are controlled on both the `<SearchFilter.Checkbox />` and
18
+ `<SearchFilter.Select />` components has changed. Previously, the string passed
19
+ on the `name` prop (which controls the field being filtered on) was also
20
+ rendered as the field label. Now, if you want a label rendered, it must be
21
+ passed on the new `label` prop. If no `label` is provided, no label will be
22
+ rendered.
23
+
24
+ ### Patch Changes
25
+
26
+ - 4aca2a5307: Introduces a `<SearchFilter.Autocomplete />` variant, which can be used as either a single- or multi-select autocomplete filter.
27
+
28
+ This variant, as well as `<SearchFilter.Select />`, now also supports loading allowed values asynchronously by passing a function that resolves the list of values to the `values` prop. (An optional `valuesDebounceMs` prop may also be provided to control the debounce time).
29
+
30
+ Check the [search plugin storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchfilter) to see how to leverage these new additions.
31
+
32
+ - Updated dependencies
33
+ - @backstage/core-components@0.8.6
34
+ - @backstage/search-common@0.2.2
35
+
3
36
  ## 0.5.6
4
37
 
5
38
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- export { c as SearchResult } from './index-1333c262.esm.js';
1
+ export { S as SearchPage } from './index-19c51c5a.esm.js';
2
2
  import '@backstage/core-plugin-api';
3
3
  import '@backstage/errors';
4
4
  import 'qs';
@@ -10,6 +10,8 @@ import '@material-ui/icons/Search';
10
10
  import '@material-ui/icons/Clear';
11
11
  import 'react-use/lib/useAsync';
12
12
  import 'react-use/lib/usePrevious';
13
+ import '@material-ui/lab';
14
+ import 'react-use/lib/useAsyncFn';
13
15
  import '@material-ui/icons/Launch';
14
16
  import '@material-ui/core/styles';
15
17
  import '@backstage/core-components';
@@ -18,11 +20,10 @@ import '@material-ui/icons/ArrowForwardIos';
18
20
  import 'react-router';
19
21
  import '@material-ui/core/InputBase';
20
22
  import '@material-ui/core/IconButton';
21
- import '@material-ui/lab';
22
23
  import '@backstage/plugin-catalog-react';
23
24
  import '@backstage/catalog-model';
24
25
  import 'react-use/lib/useEffectOnce';
25
26
  import '@material-ui/icons/ExpandMore';
26
27
  import '@material-ui/icons/FontDownload';
27
28
  import 'react-router-dom';
28
- //# sourceMappingURL=index-4d972deb.esm.js.map
29
+ //# sourceMappingURL=index-0cc5ccb8.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-0cc5ccb8.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,14 +1,16 @@
1
1
  import { createApiRef, useApi, AnalyticsContext, useAnalytics, configApiRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, createRoutableExtension, createComponentExtension, useRouteRef } from '@backstage/core-plugin-api';
2
2
  import { ResponseError } from '@backstage/errors';
3
3
  import qs from 'qs';
4
- import React, { createContext, useState, useCallback, useEffect, useContext, Fragment, cloneElement } from 'react';
4
+ import React, { createContext, useState, useCallback, useEffect, useContext, useRef, Fragment, cloneElement } from 'react';
5
5
  import FilterListIcon from '@material-ui/icons/FilterList';
6
- import { makeStyles, IconButton, Typography, Card, CardHeader, Button, Divider, CardContent, Select, MenuItem, List, ListItem, Checkbox, ListItemText, InputBase, InputAdornment, FormControl, FormLabel, FormControlLabel, InputLabel, ListItemIcon, Box, useTheme, Dialog, DialogTitle, Paper, DialogContent, Grid, DialogActions, Accordion, AccordionSummary, AccordionDetails, Tabs, Tab, Chip } from '@material-ui/core';
6
+ import { makeStyles, IconButton, Typography, Card, CardHeader, Button, Divider, CardContent, Select, MenuItem, List, ListItem, Checkbox, ListItemText, InputBase, InputAdornment, TextField, Chip, FormControl, FormLabel, FormControlLabel, InputLabel, ListItemIcon, Box, useTheme, Dialog, DialogTitle, Paper, DialogContent, Grid, DialogActions, Accordion, AccordionSummary, AccordionDetails, Tabs, Tab } from '@material-ui/core';
7
7
  import useDebounce from 'react-use/lib/useDebounce';
8
8
  import SearchIcon from '@material-ui/icons/Search';
9
9
  import ClearButton from '@material-ui/icons/Clear';
10
10
  import useAsync from 'react-use/lib/useAsync';
11
11
  import usePrevious from 'react-use/lib/usePrevious';
12
+ import { Autocomplete, Alert } from '@material-ui/lab';
13
+ import useAsyncFn from 'react-use/lib/useAsyncFn';
12
14
  import LaunchIcon from '@material-ui/icons/Launch';
13
15
  import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
14
16
  import { Link, Progress, ResponseErrorPanel, EmptyState, useContent, Table, useQueryParamState, Page, Header, Content, SidebarSearchField } from '@backstage/core-components';
@@ -17,7 +19,6 @@ import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
17
19
  import { useOutlet, useLocation } from 'react-router';
18
20
  import InputBase$1 from '@material-ui/core/InputBase';
19
21
  import IconButton$1 from '@material-ui/core/IconButton';
20
- import { Alert } from '@material-ui/lab';
21
22
  import { catalogApiRef } from '@backstage/plugin-catalog-react';
22
23
  import { ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
23
24
  import useEffectOnce from 'react-use/lib/useEffectOnce';
@@ -305,27 +306,123 @@ const SearchBar$1 = ({ onChange, ...props }) => {
305
306
  });
306
307
  };
307
308
 
308
- const useStyles$9 = makeStyles({
309
- label: {
310
- textTransform: "capitalize"
309
+ const useAsyncFilterValues = (fn, inputValue, defaultValues = [], debounce = 250) => {
310
+ const valuesMemo = useRef({});
311
+ const definiteFn = fn || (() => Promise.resolve([]));
312
+ const [state, callback] = useAsyncFn(definiteFn, [inputValue], {
313
+ loading: true
314
+ });
315
+ useDebounce(() => {
316
+ if (valuesMemo.current[inputValue] === void 0) {
317
+ valuesMemo.current[inputValue] = callback(inputValue).then((values) => {
318
+ valuesMemo.current[inputValue] = values;
319
+ return values;
320
+ });
321
+ }
322
+ }, debounce, [callback, inputValue]);
323
+ if (defaultValues.length) {
324
+ return {
325
+ loading: false,
326
+ value: defaultValues
327
+ };
311
328
  }
312
- });
313
- const CheckboxFilter = ({
314
- className,
315
- name,
316
- defaultValue,
317
- values = []
318
- }) => {
319
- const classes = useStyles$9();
320
- const { filters, setFilters } = useSearch();
329
+ const possibleValue = valuesMemo.current[inputValue];
330
+ if (Array.isArray(possibleValue)) {
331
+ return {
332
+ loading: false,
333
+ value: possibleValue
334
+ };
335
+ }
336
+ return state;
337
+ };
338
+ const useDefaultFilterValue = (name, defaultValue) => {
339
+ const { setFilters } = useSearch();
321
340
  useEffect(() => {
322
- if (Array.isArray(defaultValue)) {
341
+ if (defaultValue && [defaultValue].flat().length > 0) {
323
342
  setFilters((prevFilters) => ({
324
343
  ...prevFilters,
325
344
  [name]: defaultValue
326
345
  }));
327
346
  }
328
347
  }, []);
348
+ };
349
+
350
+ const AutocompleteFilter = (props) => {
351
+ const {
352
+ className,
353
+ defaultValue,
354
+ name,
355
+ values: givenValues,
356
+ valuesDebounceMs,
357
+ label,
358
+ filterSelectedOptions,
359
+ limitTags,
360
+ multiple
361
+ } = props;
362
+ const [inputValue, setInputValue] = useState("");
363
+ useDefaultFilterValue(name, defaultValue);
364
+ const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
365
+ const defaultValues = typeof givenValues === "function" ? void 0 : givenValues;
366
+ const { value: values, loading } = useAsyncFilterValues(asyncValues, inputValue, defaultValues, valuesDebounceMs);
367
+ const { filters, setFilters } = useSearch();
368
+ const filterValue = filters[name] || (multiple ? [] : null);
369
+ const handleChange = (_, newValue) => {
370
+ setFilters((prevState) => {
371
+ const { [name]: filter, ...others } = prevState;
372
+ if (newValue) {
373
+ return { ...others, [name]: newValue };
374
+ }
375
+ return { ...others };
376
+ });
377
+ };
378
+ const renderInput = (params) => /* @__PURE__ */ React.createElement(TextField, {
379
+ ...params,
380
+ name: "search",
381
+ variant: "outlined",
382
+ label,
383
+ fullWidth: true
384
+ });
385
+ const renderTags = (tagValue, getTagProps) => tagValue.map((option, index) => /* @__PURE__ */ React.createElement(Chip, {
386
+ label: option,
387
+ color: "primary",
388
+ ...getTagProps({ index })
389
+ }));
390
+ return /* @__PURE__ */ React.createElement(Autocomplete, {
391
+ filterSelectedOptions,
392
+ limitTags,
393
+ multiple,
394
+ className,
395
+ id: `${multiple ? "multi-" : ""}select-filter-${name}--select`,
396
+ options: values || [],
397
+ loading,
398
+ value: filterValue,
399
+ onChange: handleChange,
400
+ onInputChange: (_, newValue) => setInputValue(newValue),
401
+ renderInput,
402
+ renderTags
403
+ });
404
+ };
405
+
406
+ const useStyles$9 = makeStyles({
407
+ label: {
408
+ textTransform: "capitalize"
409
+ }
410
+ });
411
+ const CheckboxFilter = (props) => {
412
+ const {
413
+ className,
414
+ defaultValue,
415
+ label,
416
+ name,
417
+ values: givenValues = [],
418
+ valuesDebounceMs
419
+ } = props;
420
+ const classes = useStyles$9();
421
+ const { filters, setFilters } = useSearch();
422
+ useDefaultFilterValue(name, defaultValue);
423
+ const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
424
+ const defaultValues = typeof givenValues === "function" ? void 0 : givenValues;
425
+ const { value: values = [], loading } = useAsyncFilterValues(asyncValues, "", defaultValues, valuesDebounceMs);
329
426
  const handleChange = (e) => {
330
427
  const {
331
428
  target: { value, checked }
@@ -339,11 +436,12 @@ const CheckboxFilter = ({
339
436
  };
340
437
  return /* @__PURE__ */ React.createElement(FormControl, {
341
438
  className,
439
+ disabled: loading,
342
440
  fullWidth: true,
343
441
  "data-testid": "search-checkboxfilter-next"
344
- }, /* @__PURE__ */ React.createElement(FormLabel, {
442
+ }, label ? /* @__PURE__ */ React.createElement(FormLabel, {
345
443
  className: classes.label
346
- }, name), values.map((value) => {
444
+ }, label) : null, values.map((value) => {
347
445
  var _a;
348
446
  return /* @__PURE__ */ React.createElement(FormControlLabel, {
349
447
  key: value,
@@ -360,22 +458,21 @@ const CheckboxFilter = ({
360
458
  });
361
459
  }));
362
460
  };
363
- const SelectFilter = ({
364
- className,
365
- name,
366
- defaultValue,
367
- values = []
368
- }) => {
461
+ const SelectFilter = (props) => {
462
+ const {
463
+ className,
464
+ defaultValue,
465
+ label,
466
+ name,
467
+ values: givenValues,
468
+ valuesDebounceMs
469
+ } = props;
369
470
  const classes = useStyles$9();
471
+ useDefaultFilterValue(name, defaultValue);
472
+ const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
473
+ const defaultValues = typeof givenValues === "function" ? void 0 : givenValues;
474
+ const { value: values = [], loading } = useAsyncFilterValues(asyncValues, "", defaultValues, valuesDebounceMs);
370
475
  const { filters, setFilters } = useSearch();
371
- useEffect(() => {
372
- if (typeof defaultValue === "string") {
373
- setFilters((prevFilters) => ({
374
- ...prevFilters,
375
- [name]: defaultValue
376
- }));
377
- }
378
- }, []);
379
476
  const handleChange = (e) => {
380
477
  const {
381
478
  target: { value }
@@ -386,14 +483,15 @@ const SelectFilter = ({
386
483
  });
387
484
  };
388
485
  return /* @__PURE__ */ React.createElement(FormControl, {
486
+ disabled: loading,
389
487
  className,
390
488
  variant: "filled",
391
489
  fullWidth: true,
392
490
  "data-testid": "search-selectfilter-next"
393
- }, /* @__PURE__ */ React.createElement(InputLabel, {
491
+ }, label ? /* @__PURE__ */ React.createElement(InputLabel, {
394
492
  className: classes.label,
395
493
  margin: "dense"
396
- }, name), /* @__PURE__ */ React.createElement(Select, {
494
+ }, label) : null, /* @__PURE__ */ React.createElement(Select, {
397
495
  variant: "outlined",
398
496
  value: filters[name] || "",
399
497
  onChange: handleChange
@@ -404,7 +502,10 @@ const SelectFilter = ({
404
502
  value
405
503
  }, value))));
406
504
  };
407
- const SearchFilter = ({ component: Element, ...props }) => /* @__PURE__ */ React.createElement(Element, {
505
+ const SearchFilter = ({
506
+ component: Element,
507
+ ...props
508
+ }) => /* @__PURE__ */ React.createElement(Element, {
408
509
  ...props
409
510
  });
410
511
  SearchFilter.Checkbox = (props) => /* @__PURE__ */ React.createElement(SearchFilter, {
@@ -415,6 +516,10 @@ SearchFilter.Select = (props) => /* @__PURE__ */ React.createElement(SearchFilte
415
516
  ...props,
416
517
  component: SelectFilter
417
518
  });
519
+ SearchFilter.Autocomplete = (props) => /* @__PURE__ */ React.createElement(SearchFilter, {
520
+ ...props,
521
+ component: AutocompleteFilter
522
+ });
418
523
  const SearchFilterNext = SearchFilter;
419
524
 
420
525
  const DefaultResultListItem$1 = ({
@@ -511,54 +616,54 @@ const searchPlugin = createPlugin({
511
616
  });
512
617
  const SearchPage$1 = searchPlugin.provide(createRoutableExtension({
513
618
  name: "SearchPage",
514
- component: () => import('./index-cdb6d423.esm.js').then((m) => m.SearchPage),
619
+ component: () => import('./index-0cc5ccb8.esm.js').then((m) => m.SearchPage),
515
620
  mountPoint: rootRouteRef
516
621
  }));
517
622
  const SearchPageNext = searchPlugin.provide(createRoutableExtension({
518
623
  name: "SearchPageNext",
519
- component: () => import('./index-cdb6d423.esm.js').then((m) => m.SearchPage),
624
+ component: () => import('./index-0cc5ccb8.esm.js').then((m) => m.SearchPage),
520
625
  mountPoint: rootNextRouteRef
521
626
  }));
522
627
  searchPlugin.provide(createComponentExtension({
523
628
  name: "SearchBar",
524
629
  component: {
525
- lazy: () => import('./index-573ac81a.esm.js').then((m) => m.SearchBar)
630
+ lazy: () => import('./index-1d0bef15.esm.js').then((m) => m.SearchBar)
526
631
  }
527
632
  }));
528
633
  const SearchBarNext = searchPlugin.provide(createComponentExtension({
529
634
  name: "SearchBarNext",
530
635
  component: {
531
- lazy: () => import('./index-573ac81a.esm.js').then((m) => m.SearchBar)
636
+ lazy: () => import('./index-1d0bef15.esm.js').then((m) => m.SearchBar)
532
637
  }
533
638
  }));
534
639
  const SearchResult$1 = searchPlugin.provide(createComponentExtension({
535
640
  name: "SearchResult",
536
641
  component: {
537
- lazy: () => import('./index-4d972deb.esm.js').then((m) => m.SearchResult)
642
+ lazy: () => import('./index-eaf72cdc.esm.js').then((m) => m.SearchResult)
538
643
  }
539
644
  }));
540
645
  searchPlugin.provide(createComponentExtension({
541
646
  name: "SearchResultNext",
542
647
  component: {
543
- lazy: () => import('./index-4d972deb.esm.js').then((m) => m.SearchResult)
648
+ lazy: () => import('./index-eaf72cdc.esm.js').then((m) => m.SearchResult)
544
649
  }
545
650
  }));
546
651
  const SidebarSearchModal = searchPlugin.provide(createComponentExtension({
547
652
  name: "SidebarSearchModal",
548
653
  component: {
549
- lazy: () => import('./index-b4c3fd6c.esm.js').then((m) => m.SidebarSearchModal)
654
+ lazy: () => import('./index-968bef26.esm.js').then((m) => m.SidebarSearchModal)
550
655
  }
551
656
  }));
552
657
  const DefaultResultListItem = searchPlugin.provide(createComponentExtension({
553
658
  name: "DefaultResultListItem",
554
659
  component: {
555
- lazy: () => import('./index-a64e73f4.esm.js').then((m) => m.DefaultResultListItem)
660
+ lazy: () => import('./index-b4cc532f.esm.js').then((m) => m.DefaultResultListItem)
556
661
  }
557
662
  }));
558
663
  const HomePageSearchBar = searchPlugin.provide(createComponentExtension({
559
664
  name: "HomePageSearchBar",
560
665
  component: {
561
- lazy: () => import('./index-207ebc98.esm.js').then((m) => m.HomePageSearchBar)
666
+ lazy: () => import('./index-792f3072.esm.js').then((m) => m.HomePageSearchBar)
562
667
  }
563
668
  }));
564
669
 
@@ -1290,4 +1395,4 @@ const SidebarSearch = (props) => {
1290
1395
  };
1291
1396
 
1292
1397
  export { DefaultResultListItem$1 as D, Filters$1 as F, HomePageSearchBar as H, SearchPage as S, SearchBar$1 as a, SearchBarBase as b, SearchResultComponent as c, SearchModal as d, FiltersButton$1 as e, SearchContextProvider as f, SearchFilter as g, SearchFilterNext as h, SearchResultPager as i, SearchType as j, SidebarSearch as k, DefaultResultListItem as l, SearchBarNext as m, SearchPage$1 as n, SearchPageNext as o, searchPlugin as p, SearchResult$1 as q, rootRouteRef as r, searchApiRef as s, SidebarSearchModal as t, useSearch as u };
1293
- //# sourceMappingURL=index-1333c262.esm.js.map
1398
+ //# sourceMappingURL=index-19c51c5a.esm.js.map