Merge pull request #10752 from backstage/emmaindal/search-react
[Search] introduce a new search-react package.
This commit is contained in:
@@ -93,4 +93,4 @@ Additionally, the API is at a very early state, so contributing with additional
|
||||
|
||||
### Homepage Templates
|
||||
|
||||
We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/plugins/home/src/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion.
|
||||
We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion.
|
||||
|
||||
@@ -119,6 +119,19 @@ export const SettingsModal: (props: {
|
||||
children: JSX.Element;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "TemplateBackstageLogoProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "TemplateBackstageLogo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const TemplateBackstageLogo: (
|
||||
props: TemplateBackstageLogoProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TemplateBackstageLogoIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const TemplateBackstageLogoIcon: () => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const WelcomeTitle: () => JSX.Element;
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"@backstage/core-components": "^0.9.3-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.0",
|
||||
"@backstage/plugin-catalog-react": "^1.0.1-next.2",
|
||||
"@backstage/plugin-search": "^0.7.5-next.0",
|
||||
"@backstage/plugin-stack-overflow": "^0.1.0-next.0",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@backstage/config": "^1.0.0",
|
||||
|
||||
+6
-2
@@ -19,9 +19,13 @@ import React from 'react';
|
||||
type Classes = {
|
||||
svg: string;
|
||||
path: string;
|
||||
}
|
||||
};
|
||||
|
||||
export const TemplateBackstageLogo = (props: { classes: Classes }) => {
|
||||
type TemplateBackstageLogoProps = {
|
||||
classes: Classes;
|
||||
};
|
||||
|
||||
export const TemplateBackstageLogo = (props: TemplateBackstageLogoProps) => {
|
||||
return (
|
||||
<svg
|
||||
className={props.classes.svg}
|
||||
-1
@@ -43,4 +43,3 @@ export const TemplateBackstageLogoIcon = () => {
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 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 * from './TemplateBackstageLogo';
|
||||
export * from './TemplateBackstageLogoIcon';
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TemplateBackstageLogo } from '../../templates';
|
||||
import { TemplateBackstageLogo } from '../../assets';
|
||||
import { HomePageCompanyLogo } from '../../plugin';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Grid } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { ComponentAccordion } from '../../componentRenderers';
|
||||
import { HomePageToolkit } from '../../plugin';
|
||||
import { TemplateBackstageLogoIcon } from '../../templates';
|
||||
import { TemplateBackstageLogoIcon } from '../../assets';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Components/Toolkit',
|
||||
|
||||
@@ -33,6 +33,7 @@ export {
|
||||
WelcomeTitle,
|
||||
} from './plugin';
|
||||
export { SettingsModal, HeaderWorldClock } from './components';
|
||||
export * from './assets';
|
||||
export type { ClockConfig } from './components';
|
||||
export { createCardExtension } from './extensions';
|
||||
export type { ComponentRenderer } from './extensions';
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 { TemplateBackstageLogo } from './TemplateBackstageLogo';
|
||||
import { TemplateBackstageLogoIcon } from './TemplateBackstageLogoIcon';
|
||||
import {
|
||||
HomePageToolkit,
|
||||
HomePageCompanyLogo,
|
||||
HomePageStarredEntities,
|
||||
} from '../plugin';
|
||||
import { wrapInTestApp, TestApiProvider} from '@backstage/test-utils';
|
||||
import { Content, Page, InfoCard } from '@backstage/core-components';
|
||||
import {
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
HomePageSearchBar,
|
||||
SearchContextProvider,
|
||||
searchApiRef,
|
||||
searchPlugin,
|
||||
} from '@backstage/plugin-search';
|
||||
import { HomePageStackOverflowQuestions } from '@backstage/plugin-stack-overflow';
|
||||
import { Grid, makeStyles } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
const starredEntitiesApi = new MockStarredEntitiesApi();
|
||||
starredEntitiesApi.toggleStarred('component:default/example-starred-entity');
|
||||
starredEntitiesApi.toggleStarred('component:default/example-starred-entity-2');
|
||||
starredEntitiesApi.toggleStarred('component:default/example-starred-entity-3');
|
||||
starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4');
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Templates',
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
starredEntitiesApi,
|
||||
],
|
||||
[searchApiRef, { query: () => Promise.resolve({ results: [] }) }],
|
||||
[
|
||||
configApiRef,
|
||||
new ConfigReader({
|
||||
stackoverflow: {
|
||||
baseUrl: 'https://api.stackexchange.com/2.2',
|
||||
},
|
||||
}),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<Story />
|
||||
</TestApiProvider>
|
||||
</>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/hello-company': searchPlugin.routes.root,
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
searchBar: {
|
||||
display: 'flex',
|
||||
maxWidth: '60vw',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
boxShadow: theme.shadows[1],
|
||||
padding: '8px 0',
|
||||
borderRadius: '50px',
|
||||
margin: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const useLogoStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
margin: theme.spacing(5, 0),
|
||||
},
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 100,
|
||||
},
|
||||
path: {
|
||||
fill: '#7df3e1',
|
||||
},
|
||||
}));
|
||||
|
||||
export const DefaultTemplate = () => {
|
||||
const classes = useStyles();
|
||||
const { svg, path, container } = useLogoStyles();
|
||||
|
||||
return (
|
||||
<SearchContextProvider>
|
||||
<Page themeId="home">
|
||||
<Content>
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<HomePageCompanyLogo
|
||||
className={container}
|
||||
logo={<TemplateBackstageLogo classes={{ svg, path }} />}
|
||||
/>
|
||||
<Grid container item xs={12} alignItems="center" direction="row">
|
||||
<HomePageSearchBar
|
||||
classes={{ root: classes.searchBar }}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageStarredEntities />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageToolkit
|
||||
tools={Array(8).fill({
|
||||
url: '#',
|
||||
label: 'link',
|
||||
icon: <TemplateBackstageLogoIcon />,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard title="Composable Section">
|
||||
{/* placeholder for content */}
|
||||
<div style={{ height: 370 }} />
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageStackOverflowQuestions
|
||||
requestParams={{
|
||||
tagged: 'backstage',
|
||||
site: 'stackoverflow',
|
||||
pagesize: 5,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
</SearchContextProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,3 @@
|
||||
# Backstage Search
|
||||
|
||||
A search plugin library which holds functionality the [search plugin](/plugins/search/README.md) itself and other frontend plugins (e.g. [techdocs](/plugins/techdocs/README.md), [home](/plugins/home/README.md)) depend on.
|
||||
@@ -0,0 +1,61 @@
|
||||
## API Report File for "@backstage/plugin-search-react"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { ComponentProps } from 'react';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { SearchQuery } from '@backstage/plugin-search-common';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SearchApi {
|
||||
// (undocumented)
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "QueryResultProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "SearchApiProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export function SearchApiProviderForStorybook(
|
||||
props: PropsWithChildren<QueryResultProps>,
|
||||
): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const searchApiRef: ApiRef<SearchApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchContextProvider: ({
|
||||
initialState,
|
||||
children,
|
||||
}: React_2.PropsWithChildren<{
|
||||
initialState?: SearchContextState | undefined;
|
||||
}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export const SearchContextProviderForStorybook: (
|
||||
props: ComponentProps<typeof SearchContextProvider> & QueryResultProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SearchContextState = {
|
||||
term: string;
|
||||
types: string[];
|
||||
filters: JsonObject;
|
||||
pageCursor?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SearchContextValue" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const useSearch: () => SearchContextValue;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@backstage/plugin-search-react",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/search-react"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean",
|
||||
"start": "backstage-cli package start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/plugin-search-common": "^0.3.3-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.0",
|
||||
"@backstage/core-app-api": "^1.0.1-next.0",
|
||||
"react-use": "^17.3.2",
|
||||
"@backstage/types": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^1.0.1-next.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@testing-library/jest-dom": "^5.10.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const searchApiRef = createApiRef<SearchApi>({
|
||||
id: 'plugin.search.queryservice',
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface SearchApi {
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
* 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.
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 { JsonObject } from '@backstage/types';
|
||||
import { useApi, AnalyticsContext } from '@backstage/core-plugin-api';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import React, {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import useAsync, { AsyncState } from 'react-use/lib/useAsync';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { searchApiRef } from '../api';
|
||||
|
||||
type SearchContextValue = {
|
||||
result: AsyncState<SearchResultSet>;
|
||||
setTerm: React.Dispatch<React.SetStateAction<string>>;
|
||||
setTypes: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
setFilters: React.Dispatch<React.SetStateAction<JsonObject>>;
|
||||
setPageCursor: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
fetchNextPage?: React.DispatchWithoutAction;
|
||||
fetchPreviousPage?: React.DispatchWithoutAction;
|
||||
} & SearchContextState;
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SearchContextState = {
|
||||
term: string;
|
||||
types: string[];
|
||||
filters: JsonObject;
|
||||
pageCursor?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const SearchContext = createContext<SearchContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* The initial state of `SearchContextProvider`.
|
||||
*
|
||||
*/
|
||||
const searchInitialState: SearchContextState = {
|
||||
term: '',
|
||||
pageCursor: undefined,
|
||||
filters: {},
|
||||
types: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const SearchContextProvider = ({
|
||||
initialState = searchInitialState,
|
||||
children,
|
||||
}: PropsWithChildren<{ initialState?: SearchContextState }>) => {
|
||||
const searchApi = useApi(searchApiRef);
|
||||
const [pageCursor, setPageCursor] = useState<string | undefined>(
|
||||
initialState.pageCursor,
|
||||
);
|
||||
const [filters, setFilters] = useState<JsonObject>(initialState.filters);
|
||||
const [term, setTerm] = useState<string>(initialState.term);
|
||||
const [types, setTypes] = useState<string[]>(initialState.types);
|
||||
|
||||
const prevTerm = usePrevious(term);
|
||||
|
||||
const result = useAsync(
|
||||
() =>
|
||||
searchApi.query({
|
||||
term,
|
||||
filters,
|
||||
pageCursor,
|
||||
types,
|
||||
}),
|
||||
[term, filters, types, pageCursor],
|
||||
);
|
||||
|
||||
const hasNextPage =
|
||||
!result.loading && !result.error && result.value?.nextPageCursor;
|
||||
const hasPreviousPage =
|
||||
!result.loading && !result.error && result.value?.previousPageCursor;
|
||||
const fetchNextPage = useCallback(() => {
|
||||
setPageCursor(result.value?.nextPageCursor);
|
||||
}, [result.value?.nextPageCursor]);
|
||||
const fetchPreviousPage = useCallback(() => {
|
||||
setPageCursor(result.value?.previousPageCursor);
|
||||
}, [result.value?.previousPageCursor]);
|
||||
|
||||
useEffect(() => {
|
||||
// Any time a term is reset, we want to start from page 0.
|
||||
if (term && prevTerm && term !== prevTerm) {
|
||||
setPageCursor(undefined);
|
||||
}
|
||||
}, [term, prevTerm, initialState.pageCursor]);
|
||||
|
||||
const value: SearchContextValue = {
|
||||
result,
|
||||
filters,
|
||||
setFilters,
|
||||
term,
|
||||
setTerm,
|
||||
types,
|
||||
setTypes,
|
||||
pageCursor,
|
||||
setPageCursor,
|
||||
fetchNextPage: hasNextPage ? fetchNextPage : undefined,
|
||||
fetchPreviousPage: hasPreviousPage ? fetchPreviousPage : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<AnalyticsContext attributes={{ searchTypes: types.sort().join(',') }}>
|
||||
<SearchContext.Provider value={value} children={children} />
|
||||
</AnalyticsContext>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const useSearch = () => {
|
||||
const context = useContext(SearchContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useSearch must be used within a SearchContextProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
+7
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
* 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.
|
||||
@@ -17,7 +17,7 @@ import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { SearchResultSet } from '@backstage/plugin-search-common';
|
||||
import { TestApiRegistry } from '@backstage/test-utils';
|
||||
import React, { ComponentProps, PropsWithChildren } from 'react';
|
||||
import { searchApiRef } from '../../apis';
|
||||
import { searchApiRef } from '../api';
|
||||
import { SearchContextProvider as RealSearchContextProvider } from './SearchContext';
|
||||
|
||||
type QueryResultProps = {
|
||||
@@ -26,7 +26,7 @@ type QueryResultProps = {
|
||||
|
||||
/**
|
||||
* Utility context provider only for use in Storybook stories. You should use
|
||||
* the real `<SearchContextProvider>` exported by `@backstage/plugin-search` in
|
||||
* the real `<SearchContextProvider>` exported by `@backstage/plugin-search-react` in
|
||||
* your app instead of this! In some cases (like the search page) it may
|
||||
* already be provided on your behalf.
|
||||
*/
|
||||
@@ -40,6 +40,10 @@ export const SearchContextProvider = (
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility api provider only for use in Storybook stories.
|
||||
*
|
||||
*/
|
||||
export function SearchApiProvider(props: PropsWithChildren<QueryResultProps>) {
|
||||
const { mockedResults, children } = props;
|
||||
const query: any = () => Promise.resolve(mockedResults || {});
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 {
|
||||
SearchContextProvider,
|
||||
SearchContext,
|
||||
useSearch,
|
||||
} from './SearchContext';
|
||||
export type { SearchContextState } from './SearchContext';
|
||||
export {
|
||||
SearchContextProvider as SearchContextProviderForStorybook,
|
||||
SearchApiProvider as SearchApiProviderForStorybook,
|
||||
} from './SearchContextForStorybook.stories';
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 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 { searchApiRef } from './api';
|
||||
export type { SearchApi } from './api';
|
||||
export {
|
||||
SearchContextProvider,
|
||||
useSearch,
|
||||
SearchContextProviderForStorybook,
|
||||
SearchApiProviderForStorybook,
|
||||
} from './context';
|
||||
export type { SearchContextState } from './context';
|
||||
@@ -14,5 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { TemplateBackstageLogoIcon } from './TemplateBackstageLogoIcon';
|
||||
export { TemplateBackstageLogo } from './TemplateBackstageLogo'
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -83,7 +83,7 @@ export const Router: () => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export interface SearchApi {
|
||||
// (undocumented)
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
@@ -91,7 +91,7 @@ export interface SearchApi {
|
||||
|
||||
// Warning: (ae-missing-release-tag) "searchApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const searchApiRef: ApiRef<SearchApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -140,7 +140,7 @@ export type SearchBarProps = Partial<SearchBarBaseProps>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const SearchContextProvider: ({
|
||||
initialState,
|
||||
children,
|
||||
@@ -322,10 +322,11 @@ export type SidebarSearchProps = {
|
||||
icon?: IconComponent;
|
||||
};
|
||||
|
||||
// Warning: (tsdoc-at-sign-in-word) The "@" character looks like part of a TSDoc tag; use a backslash to escape it
|
||||
// Warning: (ae-forgotten-export) The symbol "SearchContextValue" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "useSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const useSearch: () => SearchContextValue;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@backstage/core-plugin-api": "^1.0.0",
|
||||
"@backstage/errors": "^1.0.0",
|
||||
"@backstage/plugin-catalog-react": "^1.0.1-next.1",
|
||||
"@backstage/plugin-search-react": "^0.0.0",
|
||||
"@backstage/plugin-search-common": "^0.3.3-next.1",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@backstage/types": "^1.0.0",
|
||||
|
||||
@@ -21,12 +21,19 @@ import {
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
|
||||
|
||||
import qs from 'qs';
|
||||
|
||||
/**
|
||||
* @deprecated import from `@backstage/plugin-search-react` instead
|
||||
*/
|
||||
export const searchApiRef = createApiRef<SearchApi>({
|
||||
id: 'plugin.search.queryservice',
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated import from `@backstage/plugin-search-react` instead
|
||||
*/
|
||||
export interface SearchApi {
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { Grid, makeStyles, Paper } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
|
||||
import { SearchBar } from './SearchBar';
|
||||
|
||||
export default {
|
||||
@@ -24,13 +24,13 @@ export default {
|
||||
component: SearchBar,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<SearchContextProvider>
|
||||
<SearchContextProviderForStorybook>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</SearchContextProviderForStorybook>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -51,6 +51,9 @@ export type SearchContextState = {
|
||||
pageCursor?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated import from `@backstage/plugin-search-react` instead
|
||||
*/
|
||||
export const SearchContext = createContext<SearchContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
@@ -62,6 +65,9 @@ const searchInitialState: SearchContextState = {
|
||||
types: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated import from `@backstage/plugin-search-react` instead
|
||||
*/
|
||||
export const SearchContextProvider = ({
|
||||
initialState = searchInitialState,
|
||||
children,
|
||||
@@ -126,6 +132,9 @@ export const SearchContextProvider = ({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated import from "@backstage/plugin-search-react" instead
|
||||
*/
|
||||
export const useSearch = () => {
|
||||
const context = useContext(SearchContext);
|
||||
if (context === undefined) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { Grid, Paper } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
|
||||
import { SearchFilter } from './SearchFilter';
|
||||
|
||||
export default {
|
||||
@@ -24,13 +24,13 @@ export default {
|
||||
component: SearchFilter,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<SearchContextProvider>
|
||||
<SearchContextProviderForStorybook>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={4}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</SearchContextProviderForStorybook>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { Button } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { rootRouteRef } from '../../plugin';
|
||||
import { SearchApiProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchApiProviderForStorybook } from '@backstage/plugin-search-react';
|
||||
import { SearchModal } from './SearchModal';
|
||||
import { useSearchModal } from './useSearchModal';
|
||||
|
||||
@@ -57,9 +57,9 @@ export default {
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<SearchApiProvider mockedResults={mockResults}>
|
||||
<SearchApiProviderForStorybook mockedResults={mockResults}>
|
||||
<Story />
|
||||
</SearchApiProvider>,
|
||||
</SearchApiProviderForStorybook>,
|
||||
{ mountedRoutes: { '/search': rootRouteRef } },
|
||||
),
|
||||
],
|
||||
|
||||
@@ -19,7 +19,8 @@ import { List, ListItem } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
|
||||
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
|
||||
import { SearchResult } from './SearchResult';
|
||||
|
||||
const mockResults = {
|
||||
@@ -57,9 +58,9 @@ export default {
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<MemoryRouter>
|
||||
<SearchContextProvider mockedResults={mockResults}>
|
||||
<SearchContextProviderForStorybook mockedResults={mockResults}>
|
||||
<Story />
|
||||
</SearchContextProvider>
|
||||
</SearchContextProviderForStorybook>
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ import CatalogIcon from '@material-ui/icons/MenuBook';
|
||||
import DocsIcon from '@material-ui/icons/Description';
|
||||
import UsersGroupsIcon from '@material-ui/icons/Person';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
|
||||
import { SearchType } from './SearchType';
|
||||
|
||||
export default {
|
||||
@@ -27,13 +27,13 @@ export default {
|
||||
component: SearchType,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<SearchContextProvider>
|
||||
<SearchContextProviderForStorybook>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={4}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</SearchContextProviderForStorybook>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@backstage/integration": "^1.1.0-next.1",
|
||||
"@backstage/integration-react": "^1.0.1-next.1",
|
||||
"@backstage/plugin-catalog-react": "^1.0.1-next.2",
|
||||
"@backstage/plugin-search": "^0.7.5-next.0",
|
||||
"@backstage/plugin-search-react": "^0.0.0",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -25,7 +25,7 @@ import React from 'react';
|
||||
import { TechDocsStorageApi, techdocsStorageApiRef } from '../../api';
|
||||
import { Reader } from './Reader';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { searchApiRef } from '@backstage/plugin-search';
|
||||
import { searchApiRef } from '@backstage/plugin-search-react';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
TechDocsStorageApi,
|
||||
} from '../../api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { searchApiRef } from '@backstage/plugin-search';
|
||||
import { searchApiRef } from '@backstage/plugin-search-react';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { searchApiRef } from '@backstage/plugin-search';
|
||||
import { searchApiRef } from '@backstage/plugin-search-react';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
act,
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { SearchContextProvider, useSearch } from '@backstage/plugin-search';
|
||||
import {
|
||||
SearchContextProvider,
|
||||
useSearch,
|
||||
} from '@backstage/plugin-search-react';
|
||||
import {
|
||||
makeStyles,
|
||||
CircularProgress,
|
||||
|
||||
Reference in New Issue
Block a user