Merge branch 'backstage:master' into master
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Adds skipMetricsLookup to the kubernetes-backend schema
|
||||
@@ -0,0 +1,283 @@
|
||||
---
|
||||
'@backstage/plugin-search-react': minor
|
||||
---
|
||||
|
||||
The `<SearchResult/>` component now accepts a optional `query` prop to request results from the search api:
|
||||
|
||||
> Note: If a query prop is not defined, the results will by default be consumed from the context.
|
||||
|
||||
Example:
|
||||
|
||||
```jsx
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
import { Grid, List, Paper } from '@material-ui/core';
|
||||
|
||||
import { Page, Header, Content, Lifecycle } from '@backstage/core-components';
|
||||
import {
|
||||
DefaultResultListItem,
|
||||
SearchBarBase,
|
||||
SearchResult,
|
||||
} from '@backstage/plugin-search-react';
|
||||
|
||||
const SearchPage = () => {
|
||||
const [query, setQuery] = useState({
|
||||
term: '',
|
||||
types: [],
|
||||
filters: {},
|
||||
});
|
||||
|
||||
const handleChange = useCallback(
|
||||
(term: string) => {
|
||||
setQuery(prevQuery => ({ ...prevQuery, term }));
|
||||
},
|
||||
[setQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title="Search" subtitle={<Lifecycle alpha />} />
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Paper>
|
||||
<SearchBarBase debounceTime={100} onChange={handleChange} />
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<SearchResult query={query}>
|
||||
{({ results }) => (
|
||||
<List>
|
||||
{results.map(({ document }) => (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</SearchResult>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Additionally, a search page can also be composed using these two new results layout components:
|
||||
|
||||
```jsx
|
||||
// Example rendering results as list
|
||||
<SearchResult>
|
||||
{({ results }) => (
|
||||
<SearchResultListLayout
|
||||
resultItems={results}
|
||||
renderResultItem={({ type, document }) => {
|
||||
switch (type) {
|
||||
case 'custom-result-item':
|
||||
return (
|
||||
<CustomResultListItem key={document.location} result={document} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SearchResult>
|
||||
```
|
||||
|
||||
```jsx
|
||||
// Example rendering results as groups
|
||||
<SearchResult>
|
||||
{({ results }) => (
|
||||
<>
|
||||
<SearchResultGroupLayout
|
||||
icon={<CustomIcon />}
|
||||
title="Custom"
|
||||
link="See all custom results"
|
||||
resultItems={results.filter(
|
||||
({ type }) => type === 'custom-result-item',
|
||||
)}
|
||||
renderResultItem={({ document }) => (
|
||||
<CustomResultListItem key={document.location} result={document} />
|
||||
)}
|
||||
/>
|
||||
<SearchResultGroupLayout
|
||||
icon={<DefaultIcon />}
|
||||
title="Default"
|
||||
resultItems={results.filter(
|
||||
({ type }) => type !== 'custom-result-item',
|
||||
)}
|
||||
renderResultItem={({ document }) => (
|
||||
<DefaultResultListItem key={document.location} result={document} />
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SearchResult>
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```jsx
|
||||
// Example using the <SearchResultList />
|
||||
const SearchPage = () => {
|
||||
const query = {
|
||||
term: 'example',
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchResultList
|
||||
query={query}
|
||||
renderResultItem={({ type, document, highlight, rank }) => {
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return (
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
icon={<CatalogIcon />}
|
||||
result={document}
|
||||
highlight={highlight}
|
||||
rank={rank}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
```jsx
|
||||
// Example using the <SearchResultGroup /> for creating a component that search and group software catalog results
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
import { MenuItem } from '@material-ui/core';
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { CatalogIcon } from '@backstage/core-components';
|
||||
import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
SearchResultGroup,
|
||||
SearchResultGroupTextFilterField,
|
||||
SearchResultGroupSelectFilterField,
|
||||
} from @backstage/plugin-search-react;
|
||||
import { SearchQuery } from '@backstage/plugin-search-common';
|
||||
|
||||
const CatalogResultsGroup = () => {
|
||||
const [query, setQuery] = useState<Partial<SearchQuery>>({
|
||||
types: ['software-catalog'],
|
||||
});
|
||||
|
||||
const filterOptions = [
|
||||
{
|
||||
label: 'Lifecycle',
|
||||
value: 'lifecycle',
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: 'owner',
|
||||
},
|
||||
];
|
||||
|
||||
const handleFilterAdd = useCallback(
|
||||
(key: string) => () => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters, [key]: undefined };
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(key: string) => (value: JsonValue) => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters, [key]: value };
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFilterDelete = useCallback(
|
||||
(key: string) => () => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters };
|
||||
delete newFilters[key];
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<CatalogIcon />}
|
||||
title="Software Catalog"
|
||||
link="See all software catalog results"
|
||||
filterOptions={filterOptions}
|
||||
renderFilterOption={({ label, value }) => (
|
||||
<MenuItem key={value} onClick={handleFilterAdd(value)}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
)}
|
||||
renderFilterField={(key: string) => {
|
||||
switch (key) {
|
||||
case 'lifecycle':
|
||||
return (
|
||||
<SearchResultGroupSelectFilterField
|
||||
key={key}
|
||||
label="Lifecycle"
|
||||
value={query.filters?.lifecycle}
|
||||
onChange={handleFilterChange('lifecycle')}
|
||||
onDelete={handleFilterDelete('lifecycle')}
|
||||
>
|
||||
<MenuItem value="production">Production</MenuItem>
|
||||
<MenuItem value="experimental">Experimental</MenuItem>
|
||||
</SearchResultGroupSelectFilterField>
|
||||
);
|
||||
case 'owner':
|
||||
return (
|
||||
<SearchResultGroupTextFilterField
|
||||
key={key}
|
||||
label="Owner"
|
||||
value={query.filters?.owner}
|
||||
onChange={handleFilterChange('owner')}
|
||||
onDelete={handleFilterDelete('owner')}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
renderResultItem={({ document, highlight, rank }) => (
|
||||
<CatalogSearchResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
highlight={highlight}
|
||||
rank={rank}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
+1
-1
@@ -202,7 +202,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi
|
||||
| [Skillz](https://skillz.com/) | [Peiman Jafari](https://github.com/peimanja) | Internal developers portal for technical documentations, components ownership and relationship, software templates and integrations with internal tools |
|
||||
| [Telus](https://www.telus.com/en/) | [Leo Li](mailto:leo.li@telus.com), [Laurent Robichaud](mailto:laurent.robichaud@telus.com), [Seb Barre](https://github.com/sbarre) | Simplifying the developer experience through centralized team member portals. Our current focus includes the adoption of Tech Docs, Software Catalog, Software Templates, the plethora of plugins, and contributing features back to Backstage. 🤖 |
|
||||
| [Fidelity Investments](https://fidelity.com) | [Ankita Upadhyay](mailto:ankita.upadhyay@fmr.com) | Getting started with the adoption for Monorepo projects |
|
||||
| [Verisk](https://verisk.com) | [Callen Barton](mailto:cbarton@verisk.com) | Developer portal to quickly create and deploy microservices. |
|
||||
| [Verisk](https://verisk.com) | [Callen Barton](mailto:#xw_architecture@verisk.com), [Kevin Johnson](mailto:#xw_architecture@verisk.com) | Developer portal to quickly create and deploy microservices. |
|
||||
| [iodigital](https://iodigital.com) | [Jan-Willem Mulder](mailto:jan-willem.mulder@iodigital.com) | Internal developer portal for discovery of applications, projects and teams. Using several plugins like the Software Catalog and Tech Insights for promoting best practices and supporting our SDLC toolchain |
|
||||
| [Fanatics](https://www.fanaticsinc.com/) | [Rory Scott](mailto:rscott@fanatics.com) | Internal Portal consolidating documentation, making it easier to manage applications, internal developer community platform, and self-service cloud infrastructure + pipelines. |
|
||||
| [Appfolio](https://appfolio.com) | [Andy Vaughn](mailto:andy.vaughn@appfolio.com) | Internal software catalog, tech radar, documentation portal to disambiguate software and domain ownership, foster exploration of available developer platform services and tools, improve communication, democratize documentation and knowledge sharing, and coordinate the software lifecycle; all in service of a best-in-class developer experience. |
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ the local `auth.environment` setting will be selected.
|
||||
> and access to a Backstage Identity Token, which can be passed to backend
|
||||
> plugins.
|
||||
|
||||
Using an authentication provide for sign-in is something you need to configure
|
||||
Using an authentication provider for sign-in is something you need to configure
|
||||
both in the frontend app, as well as the `auth` backend plugin. For information
|
||||
on how to configure the backend app, see [Sign-in Identities and Resolvers](./identity-resolver.md).
|
||||
The rest of this section will focus on how to configure sign-in for the frontend app.
|
||||
|
||||
@@ -156,15 +156,16 @@ export default async function createPlugin(
|
||||
Here is a list of Open Source custom actions that you can add to your Backstage
|
||||
scaffolder backend:
|
||||
|
||||
| Name | Package | Owner |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) |
|
||||
| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) |
|
||||
| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) |
|
||||
| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) |
|
||||
| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) |
|
||||
| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) |
|
||||
| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) |
|
||||
| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) |
|
||||
| Name | Package | Owner |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) |
|
||||
| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) |
|
||||
| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) |
|
||||
| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) |
|
||||
| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) |
|
||||
| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) |
|
||||
| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) |
|
||||
| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) |
|
||||
| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) |
|
||||
|
||||
Have fun! 🚀
|
||||
|
||||
+4
@@ -40,6 +40,8 @@ export interface Config {
|
||||
region?: string;
|
||||
/** @visibility frontend */
|
||||
skipTLSVerify?: boolean;
|
||||
/** @visibility frontend */
|
||||
skipMetricsLookup?: boolean;
|
||||
}
|
||||
| {
|
||||
/** @visibility frontend */
|
||||
@@ -62,6 +64,8 @@ export interface Config {
|
||||
oidcTokenProvider?: string;
|
||||
/** @visibility frontend */
|
||||
skipTLSVerify?: boolean;
|
||||
/** @visibility frontend */
|
||||
skipMetricsLookup?: boolean;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -11,7 +11,10 @@ import { AutocompleteProps } from '@material-ui/lab';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { InputBaseProps } from '@material-ui/core';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { LinkProps } from '@backstage/core-components';
|
||||
import { ListItemTextProps } from '@material-ui/core';
|
||||
import { ListProps } from '@material-ui/core';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
@@ -21,6 +24,7 @@ import { SearchDocument } from '@backstage/plugin-search-common';
|
||||
import { SearchQuery } from '@backstage/plugin-search-common';
|
||||
import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import { TypographyProps } from '@material-ui/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const AutocompleteFilter: (
|
||||
@@ -205,22 +209,142 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & {
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export const SearchResult: (props: SearchResultProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const SearchResultComponent: ({
|
||||
children,
|
||||
}: SearchResultProps) => JSX.Element;
|
||||
export const SearchResultApi: (props: SearchResultApiProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultApiProps = SearchResultContextProps & {
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SearchResultComponent: (props: SearchResultProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const SearchResultContext: (
|
||||
props: SearchResultContextProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultContextProps = {
|
||||
children: (state: AsyncState<SearchResultSet>) => JSX.Element;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function SearchResultGroup<FilterOption>(
|
||||
props: SearchResultGroupProps<FilterOption>,
|
||||
): JSX.Element;
|
||||
|
||||
// @public
|
||||
export const SearchResultGroupFilterFieldLayout: (
|
||||
props: SearchResultGroupFilterFieldLayoutProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupFilterFieldLayoutProps = PropsWithChildren<{
|
||||
label: string;
|
||||
value?: JsonValue;
|
||||
onDelete: () => void;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupFilterFieldPropsWith<T> = T &
|
||||
SearchResultGroupFilterFieldLayoutProps & {
|
||||
onChange: (value: JsonValue) => void;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function SearchResultGroupLayout<FilterOption>(
|
||||
props: SearchResultGroupLayoutProps<FilterOption>,
|
||||
): JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupLayoutProps<FilterOption> = ListProps & {
|
||||
icon: JSX.Element;
|
||||
title: ReactNode;
|
||||
titleProps?: Partial<TypographyProps>;
|
||||
link?: ReactNode;
|
||||
linkProps?: Partial<LinkProps>;
|
||||
filterOptions?: FilterOption[];
|
||||
renderFilterOption?: (filterOption: FilterOption) => JSX.Element;
|
||||
filterFields?: string[];
|
||||
renderFilterField?: (key: string) => JSX.Element | null;
|
||||
resultItems?: SearchResult_2[];
|
||||
renderResultItem?: (resultItem: SearchResult_2) => JSX.Element;
|
||||
error?: Error;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupProps<FilterOption> = Omit<
|
||||
SearchResultGroupLayoutProps<FilterOption>,
|
||||
'loading' | 'error' | 'resultItems' | 'filterFields'
|
||||
> & {
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SearchResultGroupSelectFilterField: (
|
||||
props: SearchResultGroupSelectFilterFieldProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupSelectFilterFieldProps =
|
||||
SearchResultGroupFilterFieldPropsWith<{
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export const SearchResultGroupTextFilterField: (
|
||||
props: SearchResultGroupTextFilterFieldProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultGroupTextFilterFieldProps =
|
||||
SearchResultGroupFilterFieldPropsWith<{}>;
|
||||
|
||||
// @public
|
||||
export const SearchResultList: (props: SearchResultListProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const SearchResultListLayout: (
|
||||
props: SearchResultListLayoutProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultListLayoutProps = ListProps & {
|
||||
resultItems?: SearchResult_2[];
|
||||
renderResultItem?: (resultItem: SearchResult_2) => JSX.Element;
|
||||
error?: Error;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SearchResultListProps = Omit<
|
||||
SearchResultListLayoutProps,
|
||||
'loading' | 'error' | 'resultItems'
|
||||
> & {
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchResultPager: () => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultProps = {
|
||||
children: (results: { results: SearchResult_2[] }) => JSX.Element;
|
||||
export type SearchResultProps = Pick<SearchResultStateProps, 'query'> & {
|
||||
children: (resultSet: SearchResultSet) => JSX.Element;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SearchResultState: (props: SearchResultStateProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SearchResultStateProps = SearchResultContextProps &
|
||||
Partial<SearchResultApiProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SelectFilter: (props: SearchFilterComponentProps) => JSX.Element;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"qs": "^6.9.4",
|
||||
"react-use": "^17.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -15,16 +15,24 @@
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { List, ListItem } from '@material-ui/core';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
|
||||
import { List, ListItem } from '@material-ui/core';
|
||||
import DefaultIcon from '@material-ui/icons/InsertDriveFile';
|
||||
import CustomIcon from '@material-ui/icons/NoteAdd';
|
||||
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { SearchDocument } from '@backstage/plugin-search-common';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
import { SearchContextProvider } from '../../context';
|
||||
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
import { SearchResultListLayout } from '../SearchResultList';
|
||||
|
||||
import { SearchResult } from './SearchResult';
|
||||
import { SearchResultGroupLayout } from '../SearchResultGroup';
|
||||
|
||||
const mockResults = {
|
||||
results: [
|
||||
@@ -55,15 +63,15 @@ const mockResults = {
|
||||
],
|
||||
};
|
||||
|
||||
const searchApiMock = new MockSearchApi(mockResults);
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchResult',
|
||||
component: SearchResult,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<MemoryRouter>
|
||||
<TestApiProvider
|
||||
apis={[[searchApiRef, new MockSearchApi(mockResults)]]}
|
||||
>
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchContextProvider>
|
||||
<Story />
|
||||
</SearchContextProvider>
|
||||
@@ -73,6 +81,17 @@ export default {
|
||||
],
|
||||
};
|
||||
|
||||
const CustomResultListItem = (props: { result: SearchDocument }) => {
|
||||
const { result } = props;
|
||||
return (
|
||||
<ListItem>
|
||||
<Link to={result.location}>
|
||||
{result.title} - {result.text}
|
||||
</Link>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<SearchResult>
|
||||
@@ -82,18 +101,17 @@ export const Default = () => {
|
||||
switch (type) {
|
||||
case 'custom-result-item':
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<ListItem>
|
||||
<Link to={document.location}>
|
||||
{document.title} - {document.text}
|
||||
</Link>
|
||||
</ListItem>
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
@@ -102,3 +120,101 @@ export const Default = () => {
|
||||
</SearchResult>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithQuery = () => {
|
||||
const query = {
|
||||
term: 'documentation',
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchResult query={query}>
|
||||
{({ results }) => (
|
||||
<List>
|
||||
{results.map(({ type, document }) => {
|
||||
switch (type) {
|
||||
case 'custom-result-item':
|
||||
return (
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
</SearchResult>
|
||||
);
|
||||
};
|
||||
|
||||
export const ListLayout = () => {
|
||||
return (
|
||||
<SearchResult>
|
||||
{({ results }) => (
|
||||
<SearchResultListLayout
|
||||
resultItems={results}
|
||||
renderResultItem={({ type, document }) => {
|
||||
switch (type) {
|
||||
case 'custom-result-item':
|
||||
return (
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SearchResult>
|
||||
);
|
||||
};
|
||||
|
||||
export const GroupLayout = () => {
|
||||
return (
|
||||
<SearchResult>
|
||||
{({ results }) => (
|
||||
<>
|
||||
<SearchResultGroupLayout
|
||||
icon={<CustomIcon />}
|
||||
title="Custom"
|
||||
link="See all custom results"
|
||||
resultItems={results.filter(
|
||||
({ type }) => type === 'custom-result-item',
|
||||
)}
|
||||
renderResultItem={({ document }) => (
|
||||
<CustomResultListItem key={document.location} result={document} />
|
||||
)}
|
||||
/>
|
||||
<SearchResultGroupLayout
|
||||
icon={<DefaultIcon />}
|
||||
title="Default"
|
||||
resultItems={results.filter(
|
||||
({ type }) => type !== 'custom-result-item',
|
||||
)}
|
||||
renderResultItem={({ document }) => (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SearchResult>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,69 +15,215 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import useAsync, { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { AnalyticsContext } from '@backstage/core-plugin-api';
|
||||
import { SearchResult } from '@backstage/plugin-search-common';
|
||||
import { AnalyticsContext, useApi } from '@backstage/core-plugin-api';
|
||||
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
|
||||
|
||||
import { useSearch } from '../../context';
|
||||
import { searchApiRef } from '../../api';
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultComponent}
|
||||
*
|
||||
* Props for {@link SearchResultContext}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultProps = {
|
||||
children: (results: { results: SearchResult[] }) => JSX.Element;
|
||||
export type SearchResultContextProps = {
|
||||
/**
|
||||
* A child function that receives an asynchronous result set and returns a react element.
|
||||
*/
|
||||
children: (state: AsyncState<SearchResultSet>) => JSX.Element;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component returning the search result.
|
||||
*
|
||||
* Provides context-based results to a child function.
|
||||
* @param props - see {@link SearchResultContextProps}.
|
||||
* @example
|
||||
* ```
|
||||
* <SearchResultContext>
|
||||
* {({ loading, error, value }) => (
|
||||
* <List>
|
||||
* {value?.map(({ document }) => (
|
||||
* <DefaultSearchResultListItem
|
||||
* key={document.location}
|
||||
* result={document}
|
||||
* />
|
||||
* ))}
|
||||
* </List>
|
||||
* )}
|
||||
* </SearchResultContext>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultComponent = ({ children }: SearchResultProps) => {
|
||||
const {
|
||||
result: { loading, error, value },
|
||||
} = useSearch();
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<ResponseErrorPanel
|
||||
title="Error encountered while fetching search results"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!value?.results.length) {
|
||||
return <EmptyState missing="data" title="Sorry, no results were found" />;
|
||||
}
|
||||
|
||||
return <>{children({ results: value.results })}</>;
|
||||
export const SearchResultContext = (props: SearchResultContextProps) => {
|
||||
const { children } = props;
|
||||
const context = useSearch();
|
||||
const state = context.result;
|
||||
return children(state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultApi}
|
||||
* @public
|
||||
*/
|
||||
const HigherOrderSearchResult = (props: SearchResultProps) => {
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{
|
||||
pluginId: 'search',
|
||||
extension: 'SearchResult',
|
||||
}}
|
||||
>
|
||||
<SearchResultComponent {...props} />
|
||||
</AnalyticsContext>
|
||||
export type SearchResultApiProps = SearchResultContextProps & {
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request results through the search api and provide them to a child function.
|
||||
* @param props - see {@link SearchResultApiProps}.
|
||||
* @example
|
||||
* ```
|
||||
* <SearchResultApi>
|
||||
* {({ loading, error, value }) => (
|
||||
* <List>
|
||||
* {value?.map(({ document }) => (
|
||||
* <DefaultSearchResultListItem
|
||||
* key={document.location}
|
||||
* result={document}
|
||||
* />
|
||||
* ))}
|
||||
* </List>
|
||||
* )}
|
||||
* </SearchResultApi>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultApi = (props: SearchResultApiProps) => {
|
||||
const { query, children } = props;
|
||||
const searchApi = useApi(searchApiRef);
|
||||
|
||||
const state = useAsync(
|
||||
() =>
|
||||
searchApi.query({
|
||||
term: query.term ?? '',
|
||||
types: query.types ?? [],
|
||||
filters: query.filters ?? {},
|
||||
pageCursor: query.pageCursor,
|
||||
}),
|
||||
[query],
|
||||
);
|
||||
|
||||
return children(state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultState}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultStateProps = SearchResultContextProps &
|
||||
Partial<SearchResultApiProps>;
|
||||
|
||||
/**
|
||||
* Call a child render function passing a search state as an argument.
|
||||
* @remarks By default, results are taken from context, but when a "query" prop is set, results are requested from the search api.
|
||||
* @param props - see {@link SearchResultStateProps}.
|
||||
* @example
|
||||
* Consuming results from context:
|
||||
* ```
|
||||
* <SearchResultState>
|
||||
* {({ loading, error, value }) => (
|
||||
* <List>
|
||||
* {value?.map(({ document }) => (
|
||||
* <DefaultSearchResultListItem
|
||||
* key={document.location}
|
||||
* result={document}
|
||||
* />
|
||||
* ))}
|
||||
* </List>
|
||||
* )}
|
||||
* </SearchResultState>
|
||||
* ```
|
||||
* @example
|
||||
* Requesting results using the search api:
|
||||
* ```
|
||||
* <SearchResultState query={{ term: 'documentation' }}>
|
||||
* {({ loading, error, value }) => (
|
||||
* <List>
|
||||
* {value?.map(({ document }) => (
|
||||
* <DefaultSearchResultListItem
|
||||
* key={document.location}
|
||||
* result={document}
|
||||
* />
|
||||
* ))}
|
||||
* </List>
|
||||
* )}
|
||||
* </SearchResultState>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultState = (props: SearchResultStateProps) => {
|
||||
const { query, children } = props;
|
||||
|
||||
return query ? (
|
||||
<SearchResultApi query={query}>{children}</SearchResultApi>
|
||||
) : (
|
||||
<SearchResultContext>{children}</SearchResultContext>
|
||||
);
|
||||
};
|
||||
|
||||
export { HigherOrderSearchResult as SearchResult };
|
||||
/**
|
||||
* Props for {@link SearchResult}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultProps = Pick<SearchResultStateProps, 'query'> & {
|
||||
children: (resultSet: SearchResultSet) => JSX.Element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders results from a parent search context or api.
|
||||
* @remarks default components for loading, error and empty variants are returned.
|
||||
* @param props - see {@link SearchResultProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultComponent = (props: SearchResultProps) => {
|
||||
const { query, children } = props;
|
||||
|
||||
return (
|
||||
<SearchResultState query={query}>
|
||||
{({ loading, error, value }) => {
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ResponseErrorPanel
|
||||
title="Error encountered while fetching search results"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!value?.results.length) {
|
||||
return (
|
||||
<EmptyState missing="data" title="Sorry, no results were found" />
|
||||
);
|
||||
}
|
||||
|
||||
return children(value);
|
||||
}}
|
||||
</SearchResultState>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A component returning the search result from a parent search context or api.
|
||||
* @param props - see {@link SearchResultProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResult = (props: SearchResultProps) => (
|
||||
<AnalyticsContext
|
||||
attributes={{
|
||||
pluginId: 'search',
|
||||
extension: 'SearchResult',
|
||||
}}
|
||||
>
|
||||
<SearchResultComponent {...props} />
|
||||
</AnalyticsContext>
|
||||
);
|
||||
|
||||
@@ -14,5 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { SearchResult, SearchResultComponent } from './SearchResult';
|
||||
export type { SearchResultProps } from './SearchResult';
|
||||
export {
|
||||
SearchResult,
|
||||
SearchResultApi,
|
||||
SearchResultContext,
|
||||
SearchResultState,
|
||||
SearchResultComponent,
|
||||
} from './SearchResult';
|
||||
|
||||
export type {
|
||||
SearchResultProps,
|
||||
SearchResultApiProps,
|
||||
SearchResultContextProps,
|
||||
SearchResultStateProps,
|
||||
} from './SearchResult';
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType, useCallback, useState } from 'react';
|
||||
|
||||
import {
|
||||
Grid,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
} from '@material-ui/core';
|
||||
import DocsIcon from '@material-ui/icons/InsertDriveFile';
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
|
||||
import {
|
||||
SearchResultGroup,
|
||||
SearchResultGroupTextFilterField,
|
||||
SearchResultGroupSelectFilterField,
|
||||
} from './SearchResultGroup';
|
||||
|
||||
const routeRef = createRouteRef({
|
||||
id: 'storybook.search.results.group.route',
|
||||
});
|
||||
|
||||
const searchApiMock = new MockSearchApi({
|
||||
results: [
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result1',
|
||||
title: 'Search Result 1',
|
||||
text: 'Some text from the search result 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
document: {
|
||||
location: 'search/search-result2',
|
||||
title: 'Search Result 2',
|
||||
text: 'Some text from the search result 2',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchResultGroup',
|
||||
component: SearchResultGroup,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TestApiProvider>,
|
||||
{ mountedRoutes: { '/': routeRef } },
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Documentation"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[searchApiRef, { query: () => new Promise<SearchResultSet>(() => {}) }],
|
||||
]}
|
||||
>
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithError = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
searchApiRef,
|
||||
{
|
||||
query: () =>
|
||||
new Promise<SearchResultSet>(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
},
|
||||
],
|
||||
]}
|
||||
>
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCustomTitle = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['custom'],
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Custom"
|
||||
titleProps={{ color: 'secondary' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCustomLink = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['custom'],
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Custom"
|
||||
link="See all custom results"
|
||||
linkProps={{ to: '/custom' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithFilters = () => {
|
||||
const [query, setQuery] = useState<Partial<SearchQuery>>({
|
||||
types: ['software-catalog'],
|
||||
});
|
||||
|
||||
const filterOptions = [
|
||||
{
|
||||
label: 'Lifecycle',
|
||||
value: 'lifecycle',
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: 'owner',
|
||||
},
|
||||
];
|
||||
|
||||
const handleFilterAdd = useCallback(
|
||||
(key: string) => () => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters, [key]: undefined };
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(key: string) => (value: JsonValue) => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters, [key]: value };
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFilterDelete = useCallback(
|
||||
(key: string) => () => {
|
||||
setQuery(prevQuery => {
|
||||
const { filters: prevFilters, ...rest } = prevQuery;
|
||||
const newFilters = { ...prevFilters };
|
||||
delete newFilters[key];
|
||||
return { ...rest, filters: newFilters };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Documentation"
|
||||
filterOptions={filterOptions}
|
||||
renderFilterOption={option => (
|
||||
<MenuItem key={option.value} onClick={handleFilterAdd(option.value)}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
)}
|
||||
renderFilterField={(key: string) => {
|
||||
switch (key) {
|
||||
case 'lifecycle':
|
||||
return (
|
||||
<SearchResultGroupSelectFilterField
|
||||
key={key}
|
||||
label="Lifecycle"
|
||||
value={query.filters?.lifecycle}
|
||||
onChange={handleFilterChange('lifecycle')}
|
||||
onDelete={handleFilterDelete('lifecycle')}
|
||||
>
|
||||
<MenuItem value="production">Production</MenuItem>
|
||||
<MenuItem value="experimental">Experimental</MenuItem>
|
||||
</SearchResultGroupSelectFilterField>
|
||||
);
|
||||
case 'owner':
|
||||
return (
|
||||
<SearchResultGroupTextFilterField
|
||||
key={key}
|
||||
label="Owner"
|
||||
value={query.filters?.owner}
|
||||
onChange={handleFilterChange('owner')}
|
||||
onDelete={handleFilterDelete('owner')}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithNoResults = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomResultListItem = (props: any) => {
|
||||
const { icon, result } = props;
|
||||
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start" divider>
|
||||
{icon && <ListItemIcon>{icon}</ListItemIcon>}
|
||||
<ListItemText
|
||||
primary={result.title}
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
secondary={result.text}
|
||||
/>
|
||||
</ListItem>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCustomResultItem = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['custom'],
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchResultGroup
|
||||
query={query}
|
||||
icon={<DocsIcon />}
|
||||
title="Custom"
|
||||
link="See all custom results"
|
||||
renderResultItem={({ document, highlight, rank }) => (
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
highlight={highlight}
|
||||
rank={rank}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { MenuItem } from '@material-ui/core';
|
||||
import DocsIcon from '@material-ui/icons/InsertDriveFile';
|
||||
|
||||
import {
|
||||
TestApiProvider,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef } from '../../api';
|
||||
import {
|
||||
SearchResultGroup,
|
||||
SearchResultGroupSelectFilterField,
|
||||
SearchResultGroupTextFilterField,
|
||||
} from './SearchResultGroup';
|
||||
|
||||
const query = jest.fn().mockResolvedValue({ results: [] });
|
||||
const searchApiMock = { query };
|
||||
|
||||
describe('SearchResultGroup', () => {
|
||||
const results = [
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result1',
|
||||
title: 'Search Result 1',
|
||||
text: 'Some text from the search result 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result2',
|
||||
title: 'Search Result 2',
|
||||
text: 'Some text from the search result 2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Renders without exploding', async () => {
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(screen.getByTitle('Docs icon')).toBeInTheDocument();
|
||||
expect(screen.getByText('Documentation')).toBeInTheDocument();
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: '',
|
||||
types: ['techdocs'],
|
||||
});
|
||||
});
|
||||
|
||||
it('Defines a default link', async () => {
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
const link = screen.getByText('See all', { exact: false });
|
||||
expect(link).toHaveAttribute('href', encodeURI('/search?types[]=techdocs'));
|
||||
});
|
||||
|
||||
it('Defines a default render result item', async () => {
|
||||
query.mockResolvedValueOnce({
|
||||
results,
|
||||
});
|
||||
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(screen.getByText('Search Result 1')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Some text from the search result 1'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Search Result 2')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Some text from the search result 2'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Could be customized with no results text', async () => {
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText('Sorry, no results were found'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Could be customized with filters', async () => {
|
||||
query.mockResolvedValueOnce({
|
||||
results,
|
||||
});
|
||||
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
filterOptions={['lifecycle', 'owner']}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('Add filter', { exact: false }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('lifecycle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('owner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Could have a text search filter field', async () => {
|
||||
query.mockResolvedValueOnce({
|
||||
results,
|
||||
});
|
||||
|
||||
const handleFilterChange = jest.fn();
|
||||
const handleFilterDelete = jest.fn();
|
||||
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
filters: { owner: null },
|
||||
}}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
filterOptions={['owner']}
|
||||
renderFilterField={(key: string) =>
|
||||
key === 'owner' ? (
|
||||
<SearchResultGroupTextFilterField
|
||||
key={key}
|
||||
label="Owner"
|
||||
onChange={handleFilterChange}
|
||||
onDelete={handleFilterDelete}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('Add filter', { exact: false }));
|
||||
|
||||
await userEvent.click(screen.getByText('owner'));
|
||||
|
||||
await userEvent.type(
|
||||
screen.getByRole('textbox'),
|
||||
'{backspace}{backspace}{backspace}{backspace}techdocs-core',
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('techdocs-core')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Could have a select search filter field', async () => {
|
||||
query.mockResolvedValueOnce({
|
||||
results,
|
||||
});
|
||||
|
||||
const handleFilterChange = jest.fn();
|
||||
const handleFilterDelete = jest.fn();
|
||||
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
filters: { lifecycle: null },
|
||||
}}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
filterOptions={['lifecycle']}
|
||||
renderFilterField={(key: string) =>
|
||||
key === 'lifecycle' ? (
|
||||
<SearchResultGroupSelectFilterField
|
||||
key={key}
|
||||
label="Lifecycle"
|
||||
onChange={handleFilterChange}
|
||||
onDelete={handleFilterDelete}
|
||||
>
|
||||
<MenuItem value="production">Production</MenuItem>
|
||||
<MenuItem value="experimental">Experimental</MenuItem>
|
||||
</SearchResultGroupSelectFilterField>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('Add filter', { exact: false }));
|
||||
|
||||
await userEvent.click(screen.getByText('lifecycle'));
|
||||
|
||||
await userEvent.click(screen.getByText('None'));
|
||||
|
||||
await userEvent.click(screen.getByText('Experimental'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleFilterChange).toHaveBeenCalledWith('experimental');
|
||||
});
|
||||
});
|
||||
|
||||
it('Shows a progress bar when loading results', async () => {
|
||||
query.mockReturnValueOnce(new Promise(() => {}));
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Shows an error panel when results rendering fails', async () => {
|
||||
query.mockRejectedValueOnce(new Error());
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultGroup
|
||||
query={{ types: ['techdocs'] }}
|
||||
icon={<DocsIcon titleAccess="Docs icon" />}
|
||||
title="Documentation"
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Error: Error encountered while fetching search results',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react';
|
||||
import qs from 'qs';
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
Theme,
|
||||
List,
|
||||
ListSubheader,
|
||||
ListItem,
|
||||
ListProps,
|
||||
Menu,
|
||||
MenuItem,
|
||||
InputBase,
|
||||
Select,
|
||||
Chip,
|
||||
Typography,
|
||||
TypographyProps,
|
||||
} from '@material-ui/core';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
import ArrowRightIcon from '@material-ui/icons/ArrowForwardIos';
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import {
|
||||
EmptyState,
|
||||
Link,
|
||||
LinkProps,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { AnalyticsContext } from '@backstage/core-plugin-api';
|
||||
import { SearchQuery, SearchResult } from '@backstage/plugin-search-common';
|
||||
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
import { SearchResultState } from '../SearchResult';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
listSubheader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
listSubheaderName: {
|
||||
marginLeft: theme.spacing(1),
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
listSubheaderChip: {
|
||||
color: theme.palette.text.secondary,
|
||||
margin: theme.spacing(0, 0, 0, 1.5),
|
||||
},
|
||||
listSubheaderFilter: {
|
||||
display: 'flex',
|
||||
color: theme.palette.text.secondary,
|
||||
margin: theme.spacing(0, 0, 0, 1.5),
|
||||
},
|
||||
listSubheaderLink: {
|
||||
marginLeft: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
listSubheaderLinkIcon: {
|
||||
fontSize: 'inherit',
|
||||
marginLeft: theme.spacing(0.5),
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultGroupFilterFieldLayout}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupFilterFieldLayoutProps = PropsWithChildren<{
|
||||
label: string;
|
||||
value?: JsonValue;
|
||||
onDelete: () => void;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Default layout for a search group filter field.
|
||||
* @param props - See {@link SearchResultGroupFilterFieldLayoutProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultGroupFilterFieldLayout = (
|
||||
props: SearchResultGroupFilterFieldLayoutProps,
|
||||
) => {
|
||||
const classes = useStyles();
|
||||
const { label, children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
{...rest}
|
||||
className={classes.listSubheaderFilter}
|
||||
variant="outlined"
|
||||
label={
|
||||
<>
|
||||
{label}: {children}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const NullIcon = () => null;
|
||||
|
||||
/**
|
||||
* Common props for a result group filter field.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupFilterFieldPropsWith<T> = T &
|
||||
SearchResultGroupFilterFieldLayoutProps & {
|
||||
onChange: (value: JsonValue) => void;
|
||||
};
|
||||
|
||||
const useSearchResultGroupTextFilterStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
fontSize: 'inherit',
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
background: theme.palette.common.white,
|
||||
},
|
||||
'&:not(:focus)': {
|
||||
cursor: 'pointer',
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultGroupTextFilterField}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupTextFilterFieldProps =
|
||||
SearchResultGroupFilterFieldPropsWith<{}>;
|
||||
|
||||
/**
|
||||
* A text field that can be used as filter on search result groups.
|
||||
* @param props - See {@link SearchResultGroupTextFilterFieldProps}.
|
||||
* @example
|
||||
* ```
|
||||
* <SearchResultGroupTextFilterField
|
||||
* id="lifecycle"
|
||||
* label="Lifecycle"
|
||||
* value={value}
|
||||
* onChange={handleChangeFilter}
|
||||
* onDelete={handleDeleteFilter}
|
||||
* />
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultGroupTextFilterField = (
|
||||
props: SearchResultGroupTextFilterFieldProps,
|
||||
) => {
|
||||
const classes = useSearchResultGroupTextFilterStyles();
|
||||
const { label, value = 'None', onChange, onDelete } = props;
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.target.value);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>
|
||||
<Typography
|
||||
role="textbox"
|
||||
component="span"
|
||||
className={classes.root}
|
||||
onChange={handleChange}
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
</SearchResultGroupFilterFieldLayout>
|
||||
);
|
||||
};
|
||||
|
||||
const useSearchResultGroupSelectFilterStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
fontSize: 'inherit',
|
||||
'&:not(:focus)': {
|
||||
cursor: 'pointer',
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
},
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
'&>div:first-child': {
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultGroupTextFilterField}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupSelectFilterFieldProps =
|
||||
SearchResultGroupFilterFieldPropsWith<{
|
||||
children: ReactNode;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A select field that can be used as filter on search result groups.
|
||||
* @param props - See {@link SearchResultGroupSelectFilterFieldProps}.
|
||||
* @example
|
||||
* ```
|
||||
* <SearchResultGroupSelectFilterField
|
||||
* id="lifecycle"
|
||||
* label="Lifecycle"
|
||||
* value={filters.lifecycle}
|
||||
* onChange={handleChangeFilter}
|
||||
* onDelete={handleDeleteFilter}
|
||||
* >
|
||||
* <MenuItem value="experimental">Experimental</MenuItem>
|
||||
* <MenuItem value="production">Production</MenuItem>
|
||||
* </SearchResultGroupSelectFilterField>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultGroupSelectFilterField = (
|
||||
props: SearchResultGroupSelectFilterFieldProps,
|
||||
) => {
|
||||
const classes = useSearchResultGroupSelectFilterStyles();
|
||||
const { label, value = 'none', onChange, onDelete, children } = props;
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<{ value: unknown }>) => {
|
||||
onChange(e.target.value as JsonValue);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>
|
||||
<Select
|
||||
className={classes.root}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
input={<InputBase />}
|
||||
IconComponent={NullIcon}
|
||||
>
|
||||
<MenuItem value="none">None</MenuItem>
|
||||
{children}
|
||||
</Select>
|
||||
</SearchResultGroupFilterFieldLayout>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultGroupLayout}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupLayoutProps<FilterOption> = ListProps & {
|
||||
/**
|
||||
* Icon that representing a result group.
|
||||
*/
|
||||
icon: JSX.Element;
|
||||
/**
|
||||
* The results group title content, it could be a text or an element.
|
||||
*/
|
||||
title: ReactNode;
|
||||
/**
|
||||
* Props for the results group title.
|
||||
*/
|
||||
titleProps?: Partial<TypographyProps>;
|
||||
/**
|
||||
* The results group link content, it could be a text or an element.
|
||||
*/
|
||||
link?: ReactNode;
|
||||
/**
|
||||
* Props for the results group link, the "to" prop defaults to "/search".
|
||||
*/
|
||||
linkProps?: Partial<LinkProps>;
|
||||
/**
|
||||
* A generic filter options that is rendered on the "Add filter" dropdown.
|
||||
*/
|
||||
filterOptions?: FilterOption[];
|
||||
/**
|
||||
* Function to customize how filter options are rendered.
|
||||
* @remarks Defaults to a menu item where its value and label bounds to the option string.
|
||||
*/
|
||||
renderFilterOption?: (filterOption: FilterOption) => JSX.Element;
|
||||
/**
|
||||
* A list of search filter keys, also known as filter field names.
|
||||
*/
|
||||
filterFields?: string[];
|
||||
/**
|
||||
* Function to customize how filter chips are rendered.
|
||||
*/
|
||||
renderFilterField?: (key: string) => JSX.Element | null;
|
||||
/**
|
||||
* Search results to be rendered as a group.
|
||||
*/
|
||||
resultItems?: SearchResult[];
|
||||
/**
|
||||
* Function to customize how result items are rendered.
|
||||
*/
|
||||
renderResultItem?: (resultItem: SearchResult) => JSX.Element;
|
||||
/**
|
||||
* If defined, will render a default error panel.
|
||||
*/
|
||||
error?: Error;
|
||||
/**
|
||||
* If defined, will render a default loading progress.
|
||||
*/
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default layout for rendering search results in a group.
|
||||
* @param props - See {@link SearchResultGroupLayoutProps}.
|
||||
* @public
|
||||
*/
|
||||
export function SearchResultGroupLayout<FilterOption>(
|
||||
props: SearchResultGroupLayoutProps<FilterOption>,
|
||||
) {
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
icon,
|
||||
title,
|
||||
titleProps = {},
|
||||
link,
|
||||
linkProps = {},
|
||||
filterOptions,
|
||||
renderFilterOption,
|
||||
filterFields,
|
||||
renderFilterField,
|
||||
resultItems,
|
||||
renderResultItem,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(e.currentTarget);
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setAnchorEl(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<List {...rest}>
|
||||
<ListSubheader className={classes.listSubheader}>
|
||||
{icon}
|
||||
<Typography
|
||||
className={classes.listSubheaderName}
|
||||
component="strong"
|
||||
{...titleProps}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{filterOptions ? (
|
||||
<Chip
|
||||
className={classes.listSubheaderChip}
|
||||
component="button"
|
||||
icon={<AddIcon />}
|
||||
variant="outlined"
|
||||
label="Add filter"
|
||||
aria-controls="filters-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
) : null}
|
||||
{filterOptions ? (
|
||||
<Menu
|
||||
id="filters-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
onClick={handleClose}
|
||||
keepMounted
|
||||
>
|
||||
{filterOptions.map(filterOption =>
|
||||
renderFilterOption ? (
|
||||
renderFilterOption(filterOption)
|
||||
) : (
|
||||
<MenuItem
|
||||
key={String(filterOption)}
|
||||
value={String(filterOption)}
|
||||
>
|
||||
{filterOption}
|
||||
</MenuItem>
|
||||
),
|
||||
)}
|
||||
</Menu>
|
||||
) : null}
|
||||
{filterFields?.map(
|
||||
filterField => renderFilterField?.(filterField) ?? null,
|
||||
)}
|
||||
<Link className={classes.listSubheaderLink} to="/search" {...linkProps}>
|
||||
{link ?? (
|
||||
<>
|
||||
See all
|
||||
<ArrowRightIcon className={classes.listSubheaderLinkIcon} />
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</ListSubheader>
|
||||
{loading ? <Progress /> : null}
|
||||
{!loading && error ? (
|
||||
<ResponseErrorPanel
|
||||
title="Error encountered while fetching search results"
|
||||
error={error}
|
||||
/>
|
||||
) : null}
|
||||
{!loading && !error && resultItems?.length
|
||||
? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null)
|
||||
: null}
|
||||
{!loading && !error && !resultItems?.length ? (
|
||||
<ListItem>
|
||||
<EmptyState missing="data" title="Sorry, no results were found" />
|
||||
</ListItem>
|
||||
) : null}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultGroup}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultGroupProps<FilterOption> = Omit<
|
||||
SearchResultGroupLayoutProps<FilterOption>,
|
||||
'loading' | 'error' | 'resultItems' | 'filterFields'
|
||||
> & {
|
||||
/**
|
||||
* A search query used for requesting the results to be grouped.
|
||||
*/
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a query, search for results and render them as a group.
|
||||
* @param props - See {@link SearchResultGroupProps}.
|
||||
* @public
|
||||
*/
|
||||
export function SearchResultGroup<FilterOption>(
|
||||
props: SearchResultGroupProps<FilterOption>,
|
||||
) {
|
||||
const {
|
||||
query,
|
||||
linkProps = {},
|
||||
renderResultItem = ({ document }) => (
|
||||
<DefaultResultListItem key={document.location} result={document} />
|
||||
),
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const to = `/search?${qs.stringify(
|
||||
{
|
||||
query: query.term,
|
||||
types: query.types,
|
||||
filters: query.filters,
|
||||
pageCursor: query.pageCursor,
|
||||
},
|
||||
{ arrayFormat: 'brackets' },
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{
|
||||
pluginId: 'search',
|
||||
extension: 'SearchResultGroup',
|
||||
}}
|
||||
>
|
||||
<SearchResultState query={query}>
|
||||
{({ loading, error, value }) => (
|
||||
<SearchResultGroupLayout
|
||||
{...rest}
|
||||
loading={loading}
|
||||
error={error}
|
||||
linkProps={{ to, ...linkProps }}
|
||||
resultItems={value?.results}
|
||||
renderResultItem={renderResultItem}
|
||||
filterFields={Object.keys(query.filters ?? {})}
|
||||
/>
|
||||
)}
|
||||
</SearchResultState>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
SearchResultGroup,
|
||||
SearchResultGroupLayout,
|
||||
SearchResultGroupTextFilterField,
|
||||
SearchResultGroupSelectFilterField,
|
||||
SearchResultGroupFilterFieldLayout,
|
||||
} from './SearchResultGroup';
|
||||
|
||||
export type {
|
||||
SearchResultGroupProps,
|
||||
SearchResultGroupLayoutProps,
|
||||
SearchResultGroupFilterFieldPropsWith,
|
||||
SearchResultGroupFilterFieldLayoutProps,
|
||||
SearchResultGroupTextFilterFieldProps,
|
||||
SearchResultGroupSelectFilterFieldProps,
|
||||
} from './SearchResultGroup';
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType, useState } from 'react';
|
||||
|
||||
import { Grid, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
|
||||
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
import { CatalogIcon, Link } from '@backstage/core-components';
|
||||
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
|
||||
import { SearchResultList } from './SearchResultList';
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
|
||||
const routeRef = createRouteRef({
|
||||
id: 'storybook.search.results.list.route',
|
||||
});
|
||||
|
||||
const searchApiMock = new MockSearchApi({
|
||||
results: [
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result1',
|
||||
title: 'Search Result 1',
|
||||
text: 'Some text from the search result 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
document: {
|
||||
location: 'search/search-result2',
|
||||
title: 'Search Result 2',
|
||||
text: 'Some text from the search result 2',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchResultList',
|
||||
component: SearchResultList,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TestApiProvider>,
|
||||
{ mountedRoutes: { '/': routeRef } },
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return <SearchResultList query={query} />;
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[searchApiRef, { query: () => new Promise<SearchResultSet>(() => {}) }],
|
||||
]}
|
||||
>
|
||||
<SearchResultList query={query} />
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithError = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
searchApiRef,
|
||||
{
|
||||
query: () =>
|
||||
new Promise<SearchResultSet>(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
},
|
||||
],
|
||||
]}
|
||||
>
|
||||
<SearchResultList query={query} />
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithNoResults = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['techdocs'],
|
||||
});
|
||||
|
||||
return (
|
||||
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
|
||||
<SearchResultList query={query} />
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomResultListItem = (props: any) => {
|
||||
const { icon, result } = props;
|
||||
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="flex-start" divider>
|
||||
{icon && <ListItemIcon>{icon}</ListItemIcon>}
|
||||
<ListItemText
|
||||
primary={result.title}
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
secondary={result.text}
|
||||
/>
|
||||
</ListItem>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCustomResultItem = () => {
|
||||
const [query] = useState<Partial<SearchQuery>>({
|
||||
types: ['custom'],
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchResultList
|
||||
query={query}
|
||||
renderResultItem={({ type, document, highlight, rank }) => {
|
||||
switch (type) {
|
||||
case 'custom':
|
||||
return (
|
||||
<CustomResultListItem
|
||||
key={document.location}
|
||||
icon={<CatalogIcon />}
|
||||
result={document}
|
||||
highlight={highlight}
|
||||
rank={rank}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<DefaultResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import {
|
||||
TestApiProvider,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef } from '../../api';
|
||||
import { SearchResultList } from './SearchResultList';
|
||||
|
||||
const query = jest.fn().mockResolvedValue({ results: [] });
|
||||
const searchApiMock = { query };
|
||||
|
||||
describe('SearchResultList', () => {
|
||||
const results = [
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result1',
|
||||
title: 'Search Result 1',
|
||||
text: 'Some text from the search result 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'techdocs',
|
||||
document: {
|
||||
location: 'search/search-result2',
|
||||
title: 'Search Result 2',
|
||||
text: 'Some text from the search result 2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Renders without exploding', async () => {
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultList
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
filters: {},
|
||||
pageCursor: undefined,
|
||||
term: '',
|
||||
types: ['techdocs'],
|
||||
});
|
||||
});
|
||||
|
||||
it('Defines a default render result item', async () => {
|
||||
query.mockResolvedValueOnce({
|
||||
results,
|
||||
});
|
||||
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultList
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(screen.getByText('Search Result 1')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Some text from the search result 1'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Search Result 2')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Some text from the search result 2'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Shows a progress bar when loading results', async () => {
|
||||
query.mockReturnValueOnce(new Promise(() => {}));
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultList
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Shows an error panel when results rendering fails', async () => {
|
||||
query.mockRejectedValueOnce(new Error());
|
||||
await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[searchApiRef, searchApiMock]]}>
|
||||
<SearchResultList
|
||||
query={{
|
||||
types: ['techdocs'],
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Error: Error encountered while fetching search results',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { List, ListProps } from '@material-ui/core';
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { AnalyticsContext } from '@backstage/core-plugin-api';
|
||||
import { SearchQuery, SearchResult } from '@backstage/plugin-search-common';
|
||||
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
import { SearchResultState } from '../SearchResult';
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultListLayout}
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultListLayoutProps = ListProps & {
|
||||
/**
|
||||
* Search results to be rendered as a list.
|
||||
*/
|
||||
resultItems?: SearchResult[];
|
||||
/**
|
||||
* Function to customize how result items are rendered.
|
||||
*/
|
||||
renderResultItem?: (resultItem: SearchResult) => JSX.Element;
|
||||
/**
|
||||
* If defined, will render a default error panel.
|
||||
*/
|
||||
error?: Error;
|
||||
/**
|
||||
* If defined, will render a default loading progress.
|
||||
*/
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default layout for rendering search results in a list.
|
||||
* @param props - See {@link SearchResultListLayoutProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultListLayout = (props: SearchResultListLayoutProps) => {
|
||||
const { loading, error, resultItems, renderResultItem, ...rest } = props;
|
||||
|
||||
return (
|
||||
<List {...rest}>
|
||||
{loading ? <Progress /> : null}
|
||||
{!loading && error ? (
|
||||
<ResponseErrorPanel
|
||||
title="Error encountered while fetching search results"
|
||||
error={error}
|
||||
/>
|
||||
) : null}
|
||||
{!loading && !error && resultItems?.length
|
||||
? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null)
|
||||
: null}
|
||||
{!loading && !error && !resultItems?.length ? (
|
||||
<EmptyState missing="data" title="Sorry, no results were found" />
|
||||
) : null}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultList}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultListProps = Omit<
|
||||
SearchResultListLayoutProps,
|
||||
'loading' | 'error' | 'resultItems'
|
||||
> & {
|
||||
/**
|
||||
* A search query used for requesting the results to be listed.
|
||||
*/
|
||||
query: Partial<SearchQuery>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a query, search for results and render them as a list.
|
||||
* @param props - See {@link SearchResultListProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultList = (props: SearchResultListProps) => {
|
||||
const {
|
||||
query,
|
||||
renderResultItem = ({ document }) => (
|
||||
<DefaultResultListItem key={document.location} result={document} />
|
||||
),
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<AnalyticsContext
|
||||
attributes={{
|
||||
pluginId: 'search',
|
||||
extension: 'SearchResultList',
|
||||
}}
|
||||
>
|
||||
<SearchResultState query={query}>
|
||||
{({ loading, error, value }) => (
|
||||
<SearchResultListLayout
|
||||
{...rest}
|
||||
loading={loading}
|
||||
error={error}
|
||||
resultItems={value?.results}
|
||||
renderResultItem={renderResultItem}
|
||||
/>
|
||||
)}
|
||||
</SearchResultState>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { SearchResultList, SearchResultListLayout } from './SearchResultList';
|
||||
|
||||
export type {
|
||||
SearchResultListProps,
|
||||
SearchResultListLayoutProps,
|
||||
} from './SearchResultList';
|
||||
@@ -43,7 +43,7 @@ export const SearchResultPager = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<nav arial-label="pagination navigation" className={classes.root}>
|
||||
<nav aria-label="pagination navigation" className={classes.root}>
|
||||
<Button
|
||||
aria-label="previous page"
|
||||
disabled={!fetchPreviousPage}
|
||||
|
||||
@@ -20,4 +20,6 @@ export * from './SearchAutocomplete';
|
||||
export * from './SearchFilter';
|
||||
export * from './SearchResult';
|
||||
export * from './SearchResultPager';
|
||||
export * from './SearchResultList';
|
||||
export * from './SearchResultGroup';
|
||||
export * from './DefaultResultListItem';
|
||||
|
||||
@@ -14,13 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { useApi, AnalyticsContext } from '@backstage/core-plugin-api';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import React, {
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
@@ -30,6 +23,15 @@ import React, {
|
||||
} from 'react';
|
||||
import useAsync, { AsyncState } from 'react-use/lib/useAsync';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { AnalyticsContext, useApi } from '@backstage/core-plugin-api';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
|
||||
import { searchApiRef } from '../api';
|
||||
|
||||
/**
|
||||
@@ -104,12 +106,13 @@ const useSearchContextValue = (
|
||||
initialValue: SearchContextState = searchInitialState,
|
||||
) => {
|
||||
const searchApi = useApi(searchApiRef);
|
||||
|
||||
const [term, setTerm] = useState<string>(initialValue.term);
|
||||
const [types, setTypes] = useState<string[]>(initialValue.types);
|
||||
const [filters, setFilters] = useState<JsonObject>(initialValue.filters);
|
||||
const [pageCursor, setPageCursor] = useState<string | undefined>(
|
||||
initialValue.pageCursor,
|
||||
);
|
||||
const [filters, setFilters] = useState<JsonObject>(initialValue.filters);
|
||||
const [term, setTerm] = useState<string>(initialValue.term);
|
||||
const [types, setTypes] = useState<string[]>(initialValue.types);
|
||||
|
||||
const prevTerm = usePrevious(term);
|
||||
|
||||
@@ -121,7 +124,7 @@ const useSearchContextValue = (
|
||||
pageCursor,
|
||||
types,
|
||||
}),
|
||||
[term, filters, types, pageCursor],
|
||||
[term, types, filters, pageCursor],
|
||||
);
|
||||
|
||||
const hasNextPage =
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
useSearch,
|
||||
useSearchContextCheck,
|
||||
} from './SearchContext';
|
||||
|
||||
export type {
|
||||
SearchContextProviderProps,
|
||||
SearchContextState,
|
||||
|
||||
+357
-238
@@ -1812,17 +1812,17 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-a11y@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addon-a11y@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addon-a11y@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/theming": 6.5.12
|
||||
axe-core: ^4.2.0
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
@@ -1839,21 +1839,21 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 8264ea473b6c8d9be3dd0f6f62849043e2834dfc78030c886ed9e75d4f6bd2617dc8a80a4a223f5d4282e32c8e1614428ce78e6c8eb734f7834877c666a53048
|
||||
checksum: f93f3c4f4dd9f2f8cfc79200d6201a385d19a3d1bb71ed4b253347db2628f7f414d3479d5545302383a8ff7580bfa5f542ddc9c22ee95ff1abc005e152193580
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-actions@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addon-actions@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addon-actions@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
fast-deep-equal: ^3.1.3
|
||||
global: ^4.4.0
|
||||
@@ -1874,23 +1874,23 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: b864ceb0ec9aef76c438cfd55977946619954e07b2b822205e5209e3901cc9ae669babc9304026e48e3717e075212c9e5175d62fd63183cf696e3e196f1f6dd8
|
||||
checksum: 94f433a6b0956e4301e5b46c68eb56f6a9b01b5ec314099611d584542369d1aec4878a5353052f963e462b1b5f3ce74070f0d03eadf30e275b0b88ef7b713dd8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-controls@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addon-controls@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addon-controls@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/store": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
lodash: ^4.17.21
|
||||
ts-dedent: ^2.0.0
|
||||
@@ -1902,19 +1902,19 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 3c8152e4a4be960a7376ab1b1dc405fb3b6eeab367684766330cfb260519420f693d19b46225fd66976b4fa16e2e888585bfa571436507b2bf10f9905dfa968e
|
||||
checksum: 27ee396ae4ab411b1bd99eacb0ebe747aa36300dc3b787d48eb685a446e64e33ff7df3b8713943132b6dbe3c78af3d2d6115b4da4c6196ec351d843690ab8a55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-links@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addon-links@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addon-links@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/router": 6.5.10
|
||||
"@storybook/router": 6.5.12
|
||||
"@types/qs": ^6.9.5
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
@@ -1930,21 +1930,21 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 5ffecdc7f1aac3d9f08ad443a05977da260f6cfbe9f9207bb9c6890dd797eb0304e41527cf70c6c9c68f69f98569ef89f5463bec57209814ff57471c1f0592d6
|
||||
checksum: 9e1394bc2fed9019537f3fee9f60abc4ea866d1688df72a435052dd07f25a8bac848d6acc2beef92af0cebc3715e9db9ae1bd95c87311a8b9fad7dee988bbd1d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-storysource@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addon-storysource@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addon-storysource@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/router": 6.5.10
|
||||
"@storybook/source-loader": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/source-loader": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
estraverse: ^5.2.0
|
||||
loader-utils: ^2.0.0
|
||||
@@ -1959,11 +1959,11 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 20dde7aa87cb2255e5cbe4992be622610ae55e32032b6dddb490f490ed35e600f38b0de48de6556dad85e88963ce2a5513e5b09ac21d40a83e3cb5fae8952a90
|
||||
checksum: 7ebaeffc1646229bebf7c9e918c225c73838b8972c00937e60928a5988440b3e6bf19225cf5f1d21c80aef59b2e20c6f473f6b5c386f5cebf7c9780d74b6ef18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addons@npm:6.5.10, @storybook/addons@npm:^6.5.9":
|
||||
"@storybook/addons@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/addons@npm:6.5.10"
|
||||
dependencies:
|
||||
@@ -1985,6 +1985,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addons@npm:6.5.12, @storybook/addons@npm:^6.0.0, @storybook/addons@npm:^6.5.9":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/addons@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
"@types/webpack-env": ^1.16.0
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
regenerator-runtime: ^0.13.7
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: c6242a80c7355544eb309603e77fdc3787d78ad983aba931f00812aeba75cc2cbd0e98c1ac0ce01441b58fabcdb671a9a799358f4bd6511cab289bc030d91f61
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/api@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/api@npm:6.5.10"
|
||||
@@ -2013,27 +2035,55 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/builder-webpack4@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/builder-webpack4@npm:6.5.10"
|
||||
"@storybook/api@npm:6.5.12, @storybook/api@npm:^6.0.0":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/api@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
fast-deep-equal: ^3.1.3
|
||||
global: ^4.4.0
|
||||
lodash: ^4.17.21
|
||||
memoizerific: ^1.11.3
|
||||
regenerator-runtime: ^0.13.7
|
||||
store2: ^2.12.0
|
||||
telejson: ^6.0.8
|
||||
ts-dedent: ^2.0.0
|
||||
util-deprecate: ^1.0.2
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: 3982cea5aaf851ccc19ff97ef82b7590d47839f0ebee28399e3b9381578edc130b4e46fe36431c62fa281949b2e0d5da2b1feafab0c2d24f70c4097d800b2679
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/builder-webpack4@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/builder-webpack4@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/channel-postmessage": 6.5.10
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/preview-web": 6.5.10
|
||||
"@storybook/router": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/channel-postmessage": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/preview-web": 6.5.12
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/ui": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
"@storybook/ui": 6.5.12
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
"@types/webpack": ^4.41.26
|
||||
autoprefixer: ^9.8.6
|
||||
@@ -2070,30 +2120,30 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 26921bbc477b8cc69a9515996f4e4a4b79ba43f783dab96930067c48ca6d127397ab7a461c25e3120468b99cad1cb641fbb85a0cd6ecf25661e2da2c182a97e6
|
||||
checksum: 3cb72ade60fc0767480c424cd5da6659027c35759852198936530c555d7fa1ae18326b3d696def06a54a55f7f26a6eb0246e675ea566c5bbfc63db54e0ad8880
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/builder-webpack5@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/builder-webpack5@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/builder-webpack5@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/channel-postmessage": 6.5.10
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/preview-web": 6.5.10
|
||||
"@storybook/router": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/channel-postmessage": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/preview-web": 6.5.12
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
babel-loader: ^8.0.0
|
||||
babel-plugin-named-exports-order: ^0.0.2
|
||||
@@ -2122,35 +2172,35 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: a2a0d7cbdcf2d1d53ec8db9c17433522525f54c9115b5ed9b576e528dd164f9036ebad0b850a00178a9b60dd9f5b8f62af4e6b19977ffe857442c8b5483349de
|
||||
checksum: 60387a186defc3b40ceae8c41376da5de65d52cc652a203bce8ef8733e54bec552143440ce5fb466916a87b0ef67b98932a96985f3ee371a3f74a9049277092f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/channel-postmessage@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/channel-postmessage@npm:6.5.10"
|
||||
"@storybook/channel-postmessage@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/channel-postmessage@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
qs: ^6.10.0
|
||||
telejson: ^6.0.8
|
||||
checksum: c0bb9cccb8071b6d68ba879f23a9eb52ee9da5563f93a235f2496838a691c7e3f7ed81e550f924bbdc305357e5a81d4b409254c7ce4d5bed53920f0ea357d4f6
|
||||
checksum: c225f848f4774e8159b9fd8bd904520ab2755f46ac6ef5a8ed7193b5cd79856e0bb797d10adfa0a1db9b9df075c41b4487d195cc451fb65b04737db70e5db6db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/channel-websocket@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/channel-websocket@npm:6.5.10"
|
||||
"@storybook/channel-websocket@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/channel-websocket@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
telejson: ^6.0.8
|
||||
checksum: e8c6df2ae02a7a257f0503cd489a2e787a419d23bc1c077868520db0ad61642001655543d594c4903f346112dc27698acfc9501c07d02bb2027d2fbb9f98f1eb
|
||||
checksum: 03d4ed3f2b67daceea06325c8705b95013b65d014dfda769a923b2c1c890d1eca1fe8eea09e3386cc572c738ce43bc8951225ebd92de4205ad80438bb9e36ebe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2165,17 +2215,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/client-api@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/client-api@npm:6.5.10"
|
||||
"@storybook/channels@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/channels@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/channel-postmessage": 6.5.10
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
core-js: ^3.8.2
|
||||
ts-dedent: ^2.0.0
|
||||
util-deprecate: ^1.0.2
|
||||
checksum: e6b240a6c62a68a485bf8f4db536df0504cfcbe9685654e5a5712b833917b9a620e91994bf2283a420e413511c967e92ead522c98ad7e6c0e88b3830ddfd4e30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/client-api@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/client-api@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/channel-postmessage": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
"@types/qs": ^6.9.5
|
||||
"@types/webpack-env": ^1.16.0
|
||||
core-js: ^3.8.2
|
||||
@@ -2192,7 +2253,7 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: c939abed09fc71b91a2813b4d817a00f717dcef6c51444b091ad3676dd0e904673252dbb9e027192e87db72aeb950a76438c5ae5829471fe18c053887049f151
|
||||
checksum: 6a103cdf1c0499e238e6a652f192b3287b7bce2a96c194c48854d1e6d8e470568aa36307aa49650e8322965295b10d601c4a087e2cb7e3515bb9ba8281aeda35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2206,13 +2267,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/components@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/components@npm:6.5.10"
|
||||
"@storybook/client-logger@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/client-logger@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/client-logger": 6.5.10
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
checksum: bd11bc25115f9b4a965e378d7dac28f9152038173ab5debb1e116a7aba69c814752d2c8aa4092dd1fc3f60cd99d4896c9e74d5e6f3c85768e7633adaf5bd2bf2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/components@npm:6.5.12, @storybook/components@npm:^6.0.0":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/components@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
memoizerific: ^1.11.3
|
||||
qs: ^6.10.0
|
||||
@@ -2221,24 +2292,24 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: ee0d520048296a4312b3018759a6b01fcc2c3fa867c64dd938e0c5ae6e4d907f599286323855128901420cd45955890e8cdb767c7b381be75d67729d89ca368a
|
||||
checksum: fa469ae615d9146df7e23f01b85731d27e6400e2d94035db172deb1f61903d86c121d858558dd12307ecc6344d21b496db020731e73eff6ace3f82672b953a93
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/core-client@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/core-client@npm:6.5.10"
|
||||
"@storybook/core-client@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/core-client@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/channel-postmessage": 6.5.10
|
||||
"@storybook/channel-websocket": 6.5.10
|
||||
"@storybook/client-api": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/channel-postmessage": 6.5.12
|
||||
"@storybook/channel-websocket": 6.5.12
|
||||
"@storybook/client-api": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/preview-web": 6.5.10
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/ui": 6.5.10
|
||||
"@storybook/preview-web": 6.5.12
|
||||
"@storybook/store": 6.5.12
|
||||
"@storybook/ui": 6.5.12
|
||||
airbnb-js-shims: ^2.2.1
|
||||
ansi-to-html: ^0.6.11
|
||||
core-js: ^3.8.2
|
||||
@@ -2256,13 +2327,13 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: c8bc4b41af51664461716dab87a176e7ac408f75568ff884b80435bbfce197ba7dd607ba83a2b36bdfbc90236e2e88848089a3ae27732a60b210fbd60ed3597c
|
||||
checksum: 4fb567964a6c15526ee6ee882e20d72c650ad0c74504ee2a058c13856efd25a0c9c7f666d36c6b4e70a75ece73c1ed812f554bbbf87771cd6171cd09ebf31410
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/core-common@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/core-common@npm:6.5.10"
|
||||
"@storybook/core-common@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/core-common@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@babel/plugin-proposal-class-properties": ^7.12.1
|
||||
@@ -2286,7 +2357,7 @@ __metadata:
|
||||
"@babel/preset-react": ^7.12.10
|
||||
"@babel/preset-typescript": ^7.12.7
|
||||
"@babel/register": ^7.12.1
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
"@types/pretty-hrtime": ^1.0.0
|
||||
@@ -2320,7 +2391,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: b3b95214a427c1ff34464c1638219fd34aa8a98b60541ec3e13d84b095be79773e5de64c958903da877e6ec52b88ff05dd9a8cd7ab0fde548ffa0db762a4ea4e
|
||||
checksum: d12b276718d3bb527084135882abc35fcdc4690896579b9f5e0417236a0d02f2791424ac62a891a0353af635be10c22db7dfe04d0db644ab0757ae3981f0ff1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2333,22 +2404,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/core-server@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/core-server@npm:6.5.10"
|
||||
"@storybook/core-events@npm:6.5.12, @storybook/core-events@npm:^6.0.0":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/core-events@npm:6.5.12"
|
||||
dependencies:
|
||||
core-js: ^3.8.2
|
||||
checksum: 82a4b9cb2a8599f3916db84b08b4cfbde8f56cb96a7afe641b3f144676fc7dc5a705e65f8844430b36ee6e6e14d6b2cb741622a8b39411276682219df5e04271
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/core-server@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/core-server@npm:6.5.12"
|
||||
dependencies:
|
||||
"@discoveryjs/json-ext": ^0.5.3
|
||||
"@storybook/builder-webpack4": 6.5.10
|
||||
"@storybook/core-client": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/builder-webpack4": 6.5.12
|
||||
"@storybook/core-client": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/csf-tools": 6.5.10
|
||||
"@storybook/manager-webpack4": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/csf-tools": 6.5.12
|
||||
"@storybook/manager-webpack4": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/telemetry": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
"@storybook/telemetry": 6.5.12
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
"@types/node-fetch": ^2.5.7
|
||||
"@types/pretty-hrtime": ^1.0.0
|
||||
@@ -2392,16 +2472,16 @@ __metadata:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 0359f8cf68e2a207d07ec631d0615c30991c78bcbe3ebe50cb8df8dd5159ab939d52789b84a50e074c027c253f74f813f745b4a002a5cf945de50a0069e0e758
|
||||
checksum: 1e7e8de948012eb126f30261d23a552e18e671ad7796c46051e17be545c37a70c72437cbb24249d2e88b3525ed6a4b7205ddeeb167aca6b9478df85b128a57d3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/core@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/core@npm:6.5.10"
|
||||
"@storybook/core@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/core@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/core-client": 6.5.10
|
||||
"@storybook/core-server": 6.5.10
|
||||
"@storybook/core-client": 6.5.12
|
||||
"@storybook/core-server": 6.5.12
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@@ -2413,13 +2493,13 @@ __metadata:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: ee80fa596cfc305138089757b1f095a0b44ed403ff1db727e99190a5d04cca84614faa816ed881b60c7ed91a4d268ee91632cb7bcaa7f2a2127424acd66a2c96
|
||||
checksum: 82606be8f89ad34a662d366e64d85af5a61270e2cff4dab8c3c3b0ec17ae3e34e560b54a1e2dd3fdd52eb072f8101b409e762f8581e65abcf07097e768baaaf0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/csf-tools@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/csf-tools@npm:6.5.10"
|
||||
"@storybook/csf-tools@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/csf-tools@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@babel/generator": ^7.12.11
|
||||
@@ -2440,7 +2520,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@storybook/mdx2-csf":
|
||||
optional: true
|
||||
checksum: 9bb4b61822760520c91da78b734a05c1f5145ad2e91f73cfe03aa900a6f40fd455c1fc2c3b1529a97a5e33246efb44462c68ad72b8dbf8f0b1811b7491411267
|
||||
checksum: 21da554c88f22ee583cd1956cf440506212d9e8727c7f0a493a92804e58b83d3fcfa18d3081a9fd1b5e8da07a1cfbee15bfa638a13a8fad585eac04fe26f5112
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2453,18 +2533,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/docs-tools@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/docs-tools@npm:6.5.10"
|
||||
"@storybook/docs-tools@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/docs-tools@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
doctrine: ^3.0.0
|
||||
lodash: ^4.17.21
|
||||
regenerator-runtime: ^0.13.7
|
||||
checksum: 7fe14992ba94c31879964001a192f338bd53399a9582c598ab1681a05efb30999059329b1f7a4cbd33947778f17e6929d0f983cf54c36cb9e371414044f5dd89
|
||||
checksum: 9433b0bc74e739f37d4be857e366d74e56566cdf27f72f462eb09a6e84713aadc8c768e075fbe7a062be104bb1536c14b8dea1e890917ca94b1580e71dbe60be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2481,19 +2561,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/manager-webpack4@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/manager-webpack4@npm:6.5.10"
|
||||
"@storybook/manager-webpack4@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/manager-webpack4@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@babel/plugin-transform-template-literals": ^7.12.1
|
||||
"@babel/preset-react": ^7.12.10
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/core-client": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/ui": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/core-client": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
"@storybook/ui": 6.5.12
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
"@types/webpack": ^4.41.26
|
||||
babel-loader: ^8.0.0
|
||||
@@ -2526,23 +2606,23 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 954f93dded7a2294cdbd7c7df93fcf1addb34d5daf46d58d8bf64d0a7e83664e34f32cd905b2b2ec771a551869e497c12f8d659a358ac079d815ca02baede6e6
|
||||
checksum: 89c6ab508a930def13403275201e1e7efd667a25f0e04a6a4fe83e83d8a77065309f11c6ba183bb412072b80580e232365789d2317ae9c1e92df92c4061752be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/manager-webpack5@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/manager-webpack5@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/manager-webpack5@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/core": ^7.12.10
|
||||
"@babel/plugin-transform-template-literals": ^7.12.1
|
||||
"@babel/preset-react": ^7.12.10
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/core-client": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/ui": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/core-client": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/theming": 6.5.12
|
||||
"@storybook/ui": 6.5.12
|
||||
"@types/node": ^14.0.10 || ^16.0.0
|
||||
babel-loader: ^8.0.0
|
||||
case-sensitive-paths-webpack-plugin: ^2.3.0
|
||||
@@ -2572,7 +2652,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 0e7542ed57cd5bef81374e88f96b4572bdea2cf0f4d09282c9794cb46641f23e085c2d6de65e224b358605e01655679e9a1933756ce728c41d1936865aa14e8a
|
||||
checksum: 701768cee510e9de024259c88ebd85ebc212d37d9f913fc7a1e13ab8751d7fb579bb99e494a439d6d5388c02e204eea8c8d91d1490b138105b414df8a384b7ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2595,29 +2675,29 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/node-logger@npm:6.5.10, @storybook/node-logger@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/node-logger@npm:6.5.10"
|
||||
"@storybook/node-logger@npm:6.5.12, @storybook/node-logger@npm:^6.5.9":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/node-logger@npm:6.5.12"
|
||||
dependencies:
|
||||
"@types/npmlog": ^4.1.2
|
||||
chalk: ^4.1.0
|
||||
core-js: ^3.8.2
|
||||
npmlog: ^5.0.1
|
||||
pretty-hrtime: ^1.0.3
|
||||
checksum: 684eddeadccb632dd0aa7d2bca62a374f71a15f07037788ee82f4d57e18ce7616304e5d8084b96dff742fe2b810843c44f26d53d4ff8f7d0706cdd81d0060fee
|
||||
checksum: 7589477486a25e67d9119e9c363e8bde23e52601043a506ac0d28f4d353f3a228face79b40a2eb0cc0c7c8b05ed084336fef5dcc3213ed4484527c6631eafeb0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/preview-web@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/preview-web@npm:6.5.10"
|
||||
"@storybook/preview-web@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/preview-web@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/channel-postmessage": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/channel-postmessage": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
ansi-to-html: ^0.6.11
|
||||
core-js: ^3.8.2
|
||||
global: ^4.4.0
|
||||
@@ -2631,7 +2711,7 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: ad4ee244101a5b9bac68373e99c95e86f3cf397d5e1093d871ad880a439dac737297bd85f4148a0b6dd3f7550e994125b4f434e113359ce9208c71cefaeab195
|
||||
checksum: e11671fd136042a0ac19be6749f40bcdf858adb6fbc36998eb2c1737fc622c15df233eb04ff3cf555a61a31d32e5218cf9ff3ff9b941b457a8159a8ca54ab2e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2654,22 +2734,22 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@storybook/react@npm:^6.5.9":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/react@npm:6.5.10"
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/react@npm:6.5.12"
|
||||
dependencies:
|
||||
"@babel/preset-flow": ^7.12.1
|
||||
"@babel/preset-react": ^7.12.10
|
||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.3
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
"@storybook/docs-tools": 6.5.10
|
||||
"@storybook/node-logger": 6.5.10
|
||||
"@storybook/docs-tools": 6.5.12
|
||||
"@storybook/node-logger": 6.5.12
|
||||
"@storybook/react-docgen-typescript-plugin": 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/store": 6.5.10
|
||||
"@storybook/store": 6.5.12
|
||||
"@types/estree": ^0.0.51
|
||||
"@types/node": ^14.14.20 || ^16.0.0
|
||||
"@types/webpack-env": ^1.16.0
|
||||
@@ -2714,7 +2794,7 @@ __metadata:
|
||||
build-storybook: bin/build.js
|
||||
start-storybook: bin/index.js
|
||||
storybook-server: bin/index.js
|
||||
checksum: 4459ee91ec8aa0159d51e9fae2d0a7bde1be4ff6fd84ae0c1a414feed2d1797e7c8b9f38b256177e3b451142ebbb64a26ac1960a15a5689ae25dbffd3fb1a7b2
|
||||
checksum: 7b762f5b0db2b94d3e492ed45a7566009dc9ff008c9b29db278d6404e1c7c9f419a0114090bf23fc5b3e193a83d74e21f0fe6ba04105eb30f59be1d4c87d652d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2734,6 +2814,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/router@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/router@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/client-logger": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
memoizerific: ^1.11.3
|
||||
qs: ^6.10.0
|
||||
regenerator-runtime: ^0.13.7
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: 545f4b767021b88f82eac69b9356fa5fa3a5866285c3a34fa762abc5743e3280895858aa5c820195d95a04c6768299191d4dd788a7d9fd3f17ff1d8236c0ba75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/semver@npm:^7.3.2":
|
||||
version: 7.3.2
|
||||
resolution: "@storybook/semver@npm:7.3.2"
|
||||
@@ -2746,12 +2842,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/source-loader@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/source-loader@npm:6.5.10"
|
||||
"@storybook/source-loader@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/source-loader@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
core-js: ^3.8.2
|
||||
estraverse: ^5.2.0
|
||||
@@ -2763,17 +2859,17 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: 77d7a0255cace96fc9953518fe54162ce4b2167b53eb744f498cf2098ba4af8074d75f572940621675303043b69e2281e8a5479ce2d331d47aa86c189cdd53bb
|
||||
checksum: ad6b0774877678d8495e7afaeb820adbc2d1b091df5678da39123eca875cc30fe41f013c2db0a4c5f7614529dc46be00cb165484e65a960d3fe7657b04cf418f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/store@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/store@npm:6.5.10"
|
||||
"@storybook/store@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/store@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/csf": 0.0.2--canary.4566f4d.1
|
||||
core-js: ^3.8.2
|
||||
fast-deep-equal: ^3.1.3
|
||||
@@ -2789,16 +2885,16 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: cd8628f9bca4fe021dbf915ac2fad02baccf6fb06568fbe8192d268d7eaaed0f96c5b42c43f9883a1528a9b46e98c7d1d06aa8c0fad4c103f5f99325979a6b89
|
||||
checksum: 7fab43471c692cda33e9cbb7abf8a932bf922bd01bf26c56a3dd909f57ff0f26a80c6456a41c5be01191bb818536f16f5c98617d263b49a60e432b0acca07949
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/telemetry@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/telemetry@npm:6.5.10"
|
||||
"@storybook/telemetry@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/telemetry@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/core-common": 6.5.10
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/core-common": 6.5.12
|
||||
chalk: ^4.1.0
|
||||
core-js: ^3.8.2
|
||||
detect-package-manager: ^2.0.1
|
||||
@@ -2809,7 +2905,7 @@ __metadata:
|
||||
nanoid: ^3.3.1
|
||||
read-pkg-up: ^7.0.1
|
||||
regenerator-runtime: ^0.13.7
|
||||
checksum: 774acc7f5d91b855be3ec1e2ae5a13b61e3eb9db2c2284ee54d788a701e637a86d4ca14597a027d32555f74392e4c99f47e886bc7729a7222e4e8159c492e054
|
||||
checksum: fe465e31e20bc271b1b066a1c1fc4ea8b7cdca1858bc875e19d7ad4e7988c0cff084ce822dc0657ecdefafa02a416dd239753c9c197f6758b39d9260964d631c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2841,19 +2937,34 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/ui@npm:6.5.10":
|
||||
version: 6.5.10
|
||||
resolution: "@storybook/ui@npm:6.5.10"
|
||||
"@storybook/theming@npm:6.5.12, @storybook/theming@npm:^6.0.0":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/theming@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.10
|
||||
"@storybook/api": 6.5.10
|
||||
"@storybook/channels": 6.5.10
|
||||
"@storybook/client-logger": 6.5.10
|
||||
"@storybook/components": 6.5.10
|
||||
"@storybook/core-events": 6.5.10
|
||||
"@storybook/router": 6.5.10
|
||||
"@storybook/client-logger": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
memoizerific: ^1.11.3
|
||||
regenerator-runtime: ^0.13.7
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: a982ebf88c7e1e21127febd17feebf26ac8d655f0c868bf110cbcaaef87eedb257300087618c525cb654808b590dc4b7b98dd6fec92fd76a040441d86c4b8289
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/ui@npm:6.5.12":
|
||||
version: 6.5.12
|
||||
resolution: "@storybook/ui@npm:6.5.12"
|
||||
dependencies:
|
||||
"@storybook/addons": 6.5.12
|
||||
"@storybook/api": 6.5.12
|
||||
"@storybook/channels": 6.5.12
|
||||
"@storybook/client-logger": 6.5.12
|
||||
"@storybook/components": 6.5.12
|
||||
"@storybook/core-events": 6.5.12
|
||||
"@storybook/router": 6.5.12
|
||||
"@storybook/semver": ^7.3.2
|
||||
"@storybook/theming": 6.5.10
|
||||
"@storybook/theming": 6.5.12
|
||||
core-js: ^3.8.2
|
||||
memoizerific: ^1.11.3
|
||||
qs: ^6.10.0
|
||||
@@ -2862,7 +2973,7 @@ __metadata:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: fc0180fc183a41b5da5a530aa8e22fd84b1934602b01bdc90b3a9865794161ffaad520e7904daf603d9fd1797dca304c4df1c5360cc1894c4971dbbca463e5dd
|
||||
checksum: 026ddc42d00773ad711824a5374b040b589e8a87b8500db4d6a8cb67263eba0789d6e6afe7fd8e0fcb798380eba210b4225af197f064d3e88ec0bda97a83b28b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10585,18 +10696,26 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"storybook-dark-mode@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "storybook-dark-mode@npm:1.1.0"
|
||||
version: 1.1.2
|
||||
resolution: "storybook-dark-mode@npm:1.1.2"
|
||||
dependencies:
|
||||
fast-deep-equal: ^3.0.0
|
||||
memoizerific: ^1.11.3
|
||||
peerDependencies:
|
||||
"@storybook/addons": ^6.0.0
|
||||
"@storybook/api": ^6.0.0
|
||||
"@storybook/components": ^6.0.0
|
||||
"@storybook/core-events": ^6.0.0
|
||||
"@storybook/theming": ^6.0.0
|
||||
checksum: e1d7abbb96d1cdbe9cdba1e20aabffa6878f170e348958cc328de4183027c2052f44f6d78f82a45039f803349c7a4ec19988d42898030b2720dc591192f520e6
|
||||
fast-deep-equal: ^3.0.0
|
||||
global: ^4.4.0
|
||||
memoizerific: ^1.11.3
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: c1522be5d7b52315e7343840bc5b6ed771e671814ccc0f63fbaa3a9f47d25c5cc0e0f5d0cdb5ac3e75e356e93d66ced8722ad2b0f7306b4113489e21cf8b07ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -6633,6 +6633,7 @@ __metadata:
|
||||
"@testing-library/react": ^12.1.3
|
||||
"@testing-library/react-hooks": ^8.0.0
|
||||
"@testing-library/user-event": ^14.0.0
|
||||
qs: ^6.9.4
|
||||
react-use: ^17.3.2
|
||||
peerDependencies:
|
||||
"@types/react": ^16.13.1 || ^17.0.0
|
||||
@@ -8128,11 +8129,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@google-cloud/container@npm:^4.0.0":
|
||||
version: 4.1.2
|
||||
resolution: "@google-cloud/container@npm:4.1.2"
|
||||
version: 4.1.3
|
||||
resolution: "@google-cloud/container@npm:4.1.3"
|
||||
dependencies:
|
||||
google-gax: ^3.3.0
|
||||
checksum: deb1b88732b2dd3dba5f7bea48fd672636fedb0d3bd894e29c8d097e087de3bfd30b53668e3686f8d879b3312be2e86d5a65cd1ba6bf630ba1c2bbe892e8bbb8
|
||||
checksum: d9554ed344e065ec16b9d281eeecd2796bf5c90149358e724596c6e2bc97c6b9ea16a9dbcf4805e29003e4bcaf3aaa85c70609b7b9b1db0fbae6c9018e55b6d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11247,13 +11248,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc@npm:0.14.1, @microsoft/tsdoc@npm:^0.14.1":
|
||||
"@microsoft/tsdoc@npm:0.14.1":
|
||||
version: 0.14.1
|
||||
resolution: "@microsoft/tsdoc@npm:0.14.1"
|
||||
checksum: e4ad038ccff2cd96e0d53ee42e2136f0f5a925b16cfda14261f1c2eb55ba0088a0e3b08ff819b476ddc69b2242a391925fab7f6ae2afabb19b96f87e19c114fc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc@npm:^0.14.1":
|
||||
version: 0.14.2
|
||||
resolution: "@microsoft/tsdoc@npm:0.14.2"
|
||||
checksum: b167c89e916ba73ee20b9c9d5dba6aa3a0de25ed3d50050e8a344dca7cd43cb2e1059bd515c820369b6e708901dd3fda476a42bc643ca74a35671ce77f724a3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/cookies@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "@mswjs/cookies@npm:0.2.0"
|
||||
@@ -14039,7 +14047,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18, @types/express-serve-static-core@npm:^4.17.5":
|
||||
"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18":
|
||||
version: 4.17.29
|
||||
resolution: "@types/express-serve-static-core@npm:4.17.29"
|
||||
dependencies:
|
||||
@@ -14061,6 +14069,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express-serve-static-core@npm:^4.17.5":
|
||||
version: 4.17.31
|
||||
resolution: "@types/express-serve-static-core@npm:4.17.31"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
"@types/qs": "*"
|
||||
"@types/range-parser": "*"
|
||||
checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express-session@npm:^1.17.2":
|
||||
version: 1.17.5
|
||||
resolution: "@types/express-session@npm:1.17.5"
|
||||
@@ -14464,9 +14483,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/marked@npm:^4.0.0":
|
||||
version: 4.0.6
|
||||
resolution: "@types/marked@npm:4.0.6"
|
||||
checksum: 223f7d8e9481287aa0274df573dd56df6cb11a1090649fabd63fd97f900ed318e730d9dfaff598aa75d8946a212c1d8959a421c5cb46d90c5a6f734a1e4a7d78
|
||||
version: 4.0.7
|
||||
resolution: "@types/marked@npm:4.0.7"
|
||||
checksum: 4907b6a606578cd864bad429aca3c234591e6ed56bd141c575140487269b825a480ace9a85e4d003d1de1f007004c9d9b2fe600038ded5bba75aef59118e58d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -22111,8 +22130,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react@npm:^7.28.0":
|
||||
version: 7.31.1
|
||||
resolution: "eslint-plugin-react@npm:7.31.1"
|
||||
version: 7.31.8
|
||||
resolution: "eslint-plugin-react@npm:7.31.8"
|
||||
dependencies:
|
||||
array-includes: ^3.1.5
|
||||
array.prototype.flatmap: ^1.3.0
|
||||
@@ -22130,7 +22149,7 @@ __metadata:
|
||||
string.prototype.matchall: ^4.0.7
|
||||
peerDependencies:
|
||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
|
||||
checksum: 6217d4c4e36c8fea24facd0cdcf22b2fd38a3603db94ec7c0a6f430046c8564b6c6884e0a9d4a4b8766201f66e8b18af594002210421bf9b6623b1fc32e15a3a
|
||||
checksum: 0683e2a624a4df6f08264a3f6bc614a81e8f961c83173bdf2d8d3523f84ed5d234cddc976dbc6815913e007c5984df742ba61be0c0592b27c3daabe0f68165a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -31058,13 +31077,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"nodemon@npm:^2.0.2":
|
||||
version: 2.0.19
|
||||
resolution: "nodemon@npm:2.0.19"
|
||||
version: 2.0.20
|
||||
resolution: "nodemon@npm:2.0.20"
|
||||
dependencies:
|
||||
chokidar: ^3.5.2
|
||||
debug: ^3.2.7
|
||||
ignore-by-default: ^1.0.1
|
||||
minimatch: ^3.0.4
|
||||
minimatch: ^3.1.2
|
||||
pstree.remy: ^1.1.8
|
||||
semver: ^5.7.1
|
||||
simple-update-notifier: ^1.0.7
|
||||
@@ -31073,7 +31092,7 @@ __metadata:
|
||||
undefsafe: ^2.0.5
|
||||
bin:
|
||||
nodemon: bin/nodemon.js
|
||||
checksum: c6cf89435a8945693fac2701285eb1f539b5003d943a1be89a9ffbfc9d0275aa7779f85a9eee509e9f19a988d53ce293266d8b35b91010e36ad9e78683f8eb07
|
||||
checksum: 9fe858682414fe703179f4fe36c86e71f40d2693b5345c09803d7b191816a6589c5df8f1f9873bffee92893880183b95a031c86340e46b364ef1b0b7f619edbf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user