diff --git a/.changeset/red-tables-train.md b/.changeset/red-tables-train.md new file mode 100644 index 0000000000..031d3720fc --- /dev/null +++ b/.changeset/red-tables-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Add a reusable pop over `EntityPeekAheadPopover` component. It shows more details about the associated entity. See the playbook here https://backstage.io/storybook/?path=/story/catalog-entitypeekaheadpopover--default diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 92bfc4de96..9528c55326 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -254,6 +254,17 @@ export class EntityOwnerFilter implements EntityFilter { // @public (undocumented) export const EntityOwnerPicker: () => JSX.Element | null; +// @public +export const EntityPeekAheadPopover: ( + props: EntityPeekAheadPopoverProps, +) => JSX.Element; + +// @public +export type EntityPeekAheadPopoverProps = PropsWithChildren<{ + entityRef: string; + delayTime?: number; +}>; + // @public (undocumented) export const EntityProcessingStatusPicker: () => JSX.Element; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 9c9757f161..14a3ce8df5 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -51,6 +51,7 @@ "classnames": "^2.2.6", "jwt-decode": "^3.1.0", "lodash": "^4.17.21", + "material-ui-popup-state": "^1.9.3", "qs": "^6.9.4", "react-use": "^17.2.4", "yaml": "^2.0.0", diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx new file mode 100644 index 0000000000..2fcb89eed1 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx @@ -0,0 +1,38 @@ +/* + * 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 { IconButton } from '@material-ui/core'; +import EmailIcon from '@material-ui/icons/Email'; +import React from 'react'; +import { Link } from '@backstage/core-components'; + +/** + * Email Card action link + * + * @private + */ +export const EmailCardAction = ({ email }: { email: string }) => { + return ( + + + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx new file mode 100644 index 0000000000..fc8b5f4007 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx @@ -0,0 +1,46 @@ +/* + * 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 { entityRouteRef } from '../../../routes'; +import { IconButton } from '@material-ui/core'; +import InfoIcon from '@material-ui/icons/Info'; +import React from 'react'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { Link } from '@backstage/core-components'; + +/** + * Card actions that show for all entities + * + * @private + */ +export const EntityCardActions = ({ entity }: { entity: Entity }) => { + const entityRoute = useRouteRef(entityRouteRef); + + return ( + + + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx new file mode 100644 index 0000000000..fca0763703 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx @@ -0,0 +1,33 @@ +/* + * 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 { EmailCardAction } from './EmailCardAction'; +import React from 'react'; +import { GroupEntity } from '@backstage/catalog-model'; + +/** + * Card actions that show for a group + * + * @private + */ +export const GroupCardActions = ({ entity }: { entity: GroupEntity }) => { + return ( + <> + {entity.spec.profile?.email && ( + + )} + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx new file mode 100644 index 0000000000..57801497f6 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx @@ -0,0 +1,33 @@ +/* + * 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 { EmailCardAction } from './EmailCardAction'; +import React from 'react'; +import { UserEntity } from '@backstage/catalog-model'; + +/** + * Card actions that show for a user + * + * @private + */ +export const UserCardActions = ({ entity }: { entity: UserEntity }) => { + return ( + <> + {entity.spec.profile?.email && ( + + )} + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts new file mode 100644 index 0000000000..75959026f7 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { EntityCardActions } from './EntityCardActions'; +export { GroupCardActions } from './GroupCardActions'; +export { UserCardActions } from './UserCardActions'; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx new file mode 100644 index 0000000000..132042452b --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx @@ -0,0 +1,197 @@ +/* + * 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 } from 'react'; +import { + EntityPeekAheadPopover, + EntityPeekAheadPopoverProps, +} from './EntityPeekAheadPopover'; +import Button from '@material-ui/core/Button'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { catalogApiRef } from '../../api'; +import { + CompoundEntityRef, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { entityRouteRef } from '../../routes'; +import { CatalogApi } from '@backstage/catalog-client'; +import { Table, TableColumn } from '@backstage/core-components'; +import { EntityRefLink } from '../EntityRefLink'; + +const mockCatalogApi = { + getEntityByRef: async (entityRef: CompoundEntityRef) => { + if ( + entityRef.namespace === 'default' && + entityRef.name === 'playback' && + entityRef.kind === 'component' + ) { + return { + kind: 'Component', + metadata: { + name: 'playback', + namespace: 'default', + description: 'Details about the playback service', + }, + }; + } + if ( + entityRef.namespace === 'default' && + entityRef.name === 'fname.lname' && + entityRef.kind === 'user' + ) { + return { + kind: 'User', + metadata: { + name: 'fname.lname', + namespace: 'default', + }, + spec: { + profile: { + email: 'fname.lname@example.com', + }, + }, + }; + } + if ( + entityRef.namespace === 'default' && + entityRef.name === 'slow.catalog.item' && + entityRef.kind === 'component' + ) { + await new Promise(resolve => setTimeout(resolve, 3000)); + return { + kind: 'Component', + metadata: { + name: 'slow.catalog.item', + namespace: 'default', + description: 'Details about the slow.catalog.item service', + }, + }; + } + return undefined; + }, +}; + +const defaultArgs = { + entityRef: 'component:default/playback', +}; + +export default { + title: 'Catalog /PeekAheadPopover', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + <> + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ), + ], +}; + +export const Default = (args: EntityPeekAheadPopoverProps) => ( + + + +); +Default.args = defaultArgs; + +export const User = (args: EntityPeekAheadPopoverProps) => ( + + + +); +User.args = { + entityRef: 'user:default/fname.lname', +}; + +export const NotFound = (args: EntityPeekAheadPopoverProps) => ( + + + +); +NotFound.args = { + entityRef: 'user:default/doesnt.exist', +}; + +export const SlowCatalogItem = (args: EntityPeekAheadPopoverProps) => ( + + + +); +SlowCatalogItem.args = { + entityRef: 'component:default/slow.catalog.item', +}; + +const columns: TableColumn[] = [ + { + title: 'entity', + render: entityRef => { + return ( + + + + ); + }, + }, + { + title: 'owner', + render: () => { + return ( + + + + ); + }, + }, + { + title: 'name', + render: entityRef => stringifyEntityRef(entityRef), + }, +]; +export const TableOfItems = (args: { data: CompoundEntityRef[] }) => ( + +); + +TableOfItems.args = { + data: [ + { + name: 'playback', + kind: 'component', + namespace: 'default', + }, + { + name: 'playback', + kind: 'component', + namespace: 'default', + }, + { + name: 'playback', + kind: 'component', + namespace: 'default', + }, + ], +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx new file mode 100644 index 0000000000..2fc630313f --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx @@ -0,0 +1,84 @@ +/* + * 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 { fireEvent, screen } from '@testing-library/react'; +import React from 'react'; +import { EntityPeekAheadPopover } from './EntityPeekAheadPopover'; +import { ApiProvider } from '@backstage/core-app-api'; +import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils'; +import { catalogApiRef } from '../../api'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { CatalogApi } from '@backstage/catalog-client'; +import { Button } from '@material-ui/core'; +import { entityRouteRef } from '../../routes'; + +const catalogApi: Partial = { + getEntityByRef: async ( + entityRef: CompoundEntityRef, + ): Promise => { + if ( + entityRef.name === 'service1' && + entityRef.namespace === 'default' && + entityRef.kind === 'component' + ) { + return { + apiVersion: '', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'service1', + }, + spec: { + tags: ['java'], + }, + }; + } + return undefined; + }, +}; + +const apis = TestApiRegistry.from([catalogApiRef, catalogApi]); + +describe('', () => { + it('renders all owners', async () => { + renderInTestApp( + + + + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + expect(screen.getByText('s1')).toBeInTheDocument(); + expect(screen.queryByText('service1')).toBeNull(); + fireEvent.mouseOver(screen.getByTestId('popover1')); + expect(await screen.findByText('service1')).toBeInTheDocument(); + + expect(screen.getByText('s2')).toBeInTheDocument(); + expect(screen.queryByText('service2')).toBeNull(); + fireEvent.mouseOver(screen.getByTestId('popover2')); + expect( + await screen.findByText('Error: service2 was not found'), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx new file mode 100644 index 0000000000..05a818fa1f --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx @@ -0,0 +1,208 @@ +/* + * 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 useAsyncFn from 'react-use/lib/useAsyncFn'; +import { catalogApiRef } from '../../api'; +import React, { PropsWithChildren, useEffect, useState } from 'react'; +import HoverPopover from 'material-ui-popup-state/HoverPopover'; +import { + bindHover, + bindPopover, + usePopupState, +} from 'material-ui-popup-state/hooks'; +import { + Box, + Card, + CardActions, + CardContent, + Chip, + makeStyles, + Tooltip, + Typography, +} from '@material-ui/core'; +import { useApiHolder } from '@backstage/core-plugin-api'; +import { + isGroupEntity, + isUserEntity, + parseEntityRef, +} from '@backstage/catalog-model'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { + EntityCardActions, + UserCardActions, + GroupCardActions, +} from './CardActionComponents'; +import { debounce } from 'lodash'; + +/** + * Properties for an entity popover on hover of a component. + * + * @public + */ +export type EntityPeekAheadPopoverProps = PropsWithChildren<{ + entityRef: string; + delayTime?: number; +}>; + +const useStyles = makeStyles(() => { + return { + trigger: { + display: 'inline-block', + }, + popoverPaper: { + width: '30em', + }, + descriptionTypography: { + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + }, + }; +}); + +const maxTagChips = 4; + +/** + * Shows an entity popover on hover of a component. + * + * @public + */ +export const EntityPeekAheadPopover = (props: EntityPeekAheadPopoverProps) => { + const { entityRef, children, delayTime = 500 } = props; + + const classes = useStyles(); + const apiHolder = useApiHolder(); + const popupState = usePopupState({ + variant: 'popover', + popupId: 'entity-peek-ahead', + }); + const compoundEntityRef = parseEntityRef(entityRef); + const [isHovered, setIsHovered] = useState(false); + + const debouncedHandleMouseEnter = debounce( + () => setIsHovered(true), + delayTime, + ); + + const [{ loading, error, value: entity }, load] = useAsyncFn(async () => { + const catalogApi = apiHolder.get(catalogApiRef); + if (catalogApi) { + const retrievedEntity = await catalogApi.getEntityByRef( + compoundEntityRef, + ); + if (!retrievedEntity) { + throw new Error(`${compoundEntityRef.name} was not found`); + } + return retrievedEntity; + } + return undefined; + }, [apiHolder, compoundEntityRef]); + + const handleOnMouseLeave = () => { + setIsHovered(false); + debouncedHandleMouseEnter.cancel(); + }; + + useEffect(() => { + if (popupState.isOpen && !entity && !error && !loading) { + load(); + } + }, [popupState.isOpen, load, entity, error, loading]); + + return ( + <> + + + {children} + + + {isHovered && ( + + <> + {error && } + + {loading && } + + + {entity && ( + <> + + {compoundEntityRef.namespace} + + + {compoundEntityRef.name} + + {entity.kind} + + {entity.metadata.description} + + {entity.spec?.type} + + {(entity.metadata.tags || []) + .slice(0, maxTagChips) + .map(tag => { + return ; + })} + {entity.metadata.tags?.length && + entity.metadata.tags?.length > maxTagChips && ( + + + + )} + + + )} + + {!error && ( + + {entity && ( + <> + {isUserEntity(entity) && ( + + )} + {isGroupEntity(entity) && ( + + )} + + + )} + + )} + + + + )} + + ); +}; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts b/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts new file mode 100644 index 0000000000..6630f6f555 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/index.ts @@ -0,0 +1,16 @@ +/* + * 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 * from './EntityPeekAheadPopover'; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx new file mode 100644 index 0000000000..e82c4ab963 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx @@ -0,0 +1,41 @@ +/* + * 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 } from 'react'; +import { EntityRefLink, EntityRefLinkProps } from './EntityRefLink'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { entityRouteRef } from '../../routes'; + +const defaultArgs = { + entityRef: 'component:default/playback', +}; + +export default { + title: 'Catalog /EntityRefLink', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp(, { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }), + ], +}; + +export const Default = (args: EntityRefLinkProps) => ( + +); +Default.args = defaultArgs; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx new file mode 100644 index 0000000000..176dd24530 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx @@ -0,0 +1,42 @@ +/* + * 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 } from 'react'; +import { EntityRefLinks, EntityRefLinksProps } from './EntityRefLinks'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { entityRouteRef } from '../../routes'; + +const defaultArgs = { + entityRefs: ['component:default/playback', 'user:default/fname.lname'], +}; + +export default { + title: 'Catalog /EntityRefLinks', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp(, { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }), + ], +}; + +export const Default = ( + args: EntityRefLinksProps, +) => ; +Default.args = defaultArgs; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index c604306ead..a4380ddef0 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -19,6 +19,7 @@ export * from './EntityKindPicker'; export * from './EntityLifecyclePicker'; export * from './EntityOwnerPicker'; export * from './EntityRefLink'; +export * from './EntityPeekAheadPopover'; export * from './EntitySearchBar'; export * from './EntityTable'; export * from './EntityTagPicker'; diff --git a/storybook/.storybook/main.js b/storybook/.storybook/main.js index e523b68337..3d92237438 100644 --- a/storybook/.storybook/main.js +++ b/storybook/.storybook/main.js @@ -12,6 +12,7 @@ const BACKSTAGE_CORE_STORIES = [ 'plugins/search-react', 'plugins/home', 'plugins/stack-overflow', + 'plugins/catalog-react', ]; // Some configuration needs to be available directly on the exported object diff --git a/yarn.lock b/yarn.lock index fa39818052..a47f5bad21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5477,6 +5477,7 @@ __metadata: cross-fetch: ^3.1.5 jwt-decode: ^3.1.0 lodash: ^4.17.21 + material-ui-popup-state: ^1.9.3 qs: ^6.9.4 react-test-renderer: ^16.13.1 react-use: ^17.2.4