diff --git a/.changeset/old-foxes-shave.md b/.changeset/old-foxes-shave.md new file mode 100644 index 0000000000..4cd0808103 --- /dev/null +++ b/.changeset/old-foxes-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Add ApiDefinitionDialog component for fast access to the API definition in ProvidedApiCards and ConsumedApiCards. diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 0872d97355..98a4f2cd96 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -20,6 +20,13 @@ import { UserListFilterKind } from '@backstage/plugin-catalog-react'; // @public (undocumented) export const ApiDefinitionCard: () => JSX.Element; +// @public +export function ApiDefinitionDialog(props: { + open: boolean; + entity: ApiEntity; + onClose: () => void; +}): JSX.Element; + // @public (undocumented) export type ApiDefinitionWidget = { type: string; diff --git a/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx b/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx new file mode 100644 index 0000000000..2f82a0cc5a --- /dev/null +++ b/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx @@ -0,0 +1,176 @@ +/* + * Copyright 2023 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 { ApiEntity } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { BackstageTheme } from '@backstage/theme'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + makeStyles, + Tab, + Tabs, + Typography, +} from '@material-ui/core'; +import React, { useEffect } from 'react'; +import { apiDocsConfigRef } from '../../config'; +import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget'; + +const useStyles = makeStyles(theme => ({ + fullHeightDialog: { + height: 'calc(100% - 64px)', + }, + root: { + display: 'flex', + flexGrow: 1, + width: '100%', + height: '100%', + }, + tabs: { + borderRight: `1px solid ${theme.palette.divider}`, + flexShrink: 0, + }, + tabContents: { + flexGrow: 1, + overflowX: 'auto', + }, + title: { + color: theme.palette.text.primary, + wordBreak: 'break-word', + fontSize: theme.typography.h3.fontSize, + marginBottom: 0, + }, + type: { + textTransform: 'uppercase', + fontSize: 11, + opacity: 0.8, + marginBottom: theme.spacing(1), + color: theme.palette.text.primary, + }, +})); + +function TabPanel(props: { + children?: React.ReactNode; + index: number; + value: number; +}) { + const { children, value, index, ...other } = props; + const classes = useStyles(); + return ( + + ); +} + +function a11yProps(index: number) { + return { + id: `vertical-tab-${index}`, + 'aria-controls': `vertical-tabpanel-${index}`, + }; +} + +/** + * A dialog that lets users inspect the API definition. + * + * @public + */ +export function ApiDefinitionDialog(props: { + open: boolean; + entity: ApiEntity; + onClose: () => void; +}) { + const { open, entity, onClose } = props; + const [activeTab, setActiveTab] = React.useState(0); + const classes = useStyles(); + + useEffect(() => { + setActiveTab(0); + }, [open]); + + const config = useApi(apiDocsConfigRef); + const definitionWidget = config.getApiDefinitionWidget(entity); + + let tabIndex = 0; + let tabPanelIndex = 0; + + return ( + + + + API - {definitionWidget?.title ?? 'Raw'} + + + {entity.metadata.title ?? entity.metadata.name} + + + + setActiveTab(newValue)} + aria-label="API definition options" + className={classes.tabs} + > + {definitionWidget ? ( + + ) : null} + + + + {definitionWidget ? ( + + {definitionWidget.component(entity.spec.definition)} + + ) : null} + + + + + + + + + ); +} diff --git a/plugins/api-docs/src/components/ApiDefinitionDialog/index.ts b/plugins/api-docs/src/components/ApiDefinitionDialog/index.ts new file mode 100644 index 0000000000..28e3388bcc --- /dev/null +++ b/plugins/api-docs/src/components/ApiDefinitionDialog/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 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 { ApiDefinitionDialog } from './ApiDefinitionDialog'; diff --git a/plugins/api-docs/src/components/ApisCards/presets.tsx b/plugins/api-docs/src/components/ApisCards/presets.tsx index 4a0a0f429e..b1ef39c113 100644 --- a/plugins/api-docs/src/components/ApisCards/presets.tsx +++ b/plugins/api-docs/src/components/ApisCards/presets.tsx @@ -15,10 +15,13 @@ */ import { ApiEntity } from '@backstage/catalog-model'; -import { EntityTable } from '@backstage/plugin-catalog-react'; -import React from 'react'; -import { ApiTypeTitle } from '../ApiDefinitionCard'; import { TableColumn } from '@backstage/core-components'; +import { EntityTable } from '@backstage/plugin-catalog-react'; +import ExtensionIcon from '@material-ui/icons/Extension'; +import { ToggleButton } from '@material-ui/lab'; +import React, { useState } from 'react'; +import { ApiTypeTitle } from '../ApiDefinitionCard'; +import { ApiDefinitionDialog } from '../ApiDefinitionDialog'; export function createSpecApiTypeColumn(): TableColumn { return { @@ -28,9 +31,29 @@ export function createSpecApiTypeColumn(): TableColumn { }; } -// TODO: This could be moved to plugin-catalog-react if we wouldn't have a -// special createSpecApiTypeColumn. But this is required to use ApiTypeTitle to -// resolve the display name of an entity. Is the display name really worth it? +const ApiDefinitionButton = ({ apiEntity }: { apiEntity: ApiEntity }) => { + const [dialogOpen, setDialogOpen] = useState(false); + + return ( + <> + setDialogOpen(!dialogOpen)}> + + + setDialogOpen(false)} + /> + + ); +}; + +function createApiDefinitionColumn(): TableColumn { + return { + title: 'API Definition', + render: entity => , + }; +} export const apiEntityColumns: TableColumn[] = [ EntityTable.columns.createEntityRefColumn({ defaultKind: 'API' }), @@ -39,4 +62,5 @@ export const apiEntityColumns: TableColumn[] = [ createSpecApiTypeColumn(), EntityTable.columns.createSpecLifecycleColumn(), EntityTable.columns.createMetadataDescriptionColumn(), + createApiDefinitionColumn(), ]; diff --git a/plugins/api-docs/src/components/index.ts b/plugins/api-docs/src/components/index.ts index ea40076af4..ba3747d42f 100644 --- a/plugins/api-docs/src/components/index.ts +++ b/plugins/api-docs/src/components/index.ts @@ -16,6 +16,7 @@ export * from './ApiExplorerPage'; export * from './ApiDefinitionCard'; +export * from './ApiDefinitionDialog'; export * from './ApisCards'; export * from './AsyncApiDefinitionWidget'; export * from './ComponentsCards';