Merge pull request #2279 from SDA-SE/feat/refactor-api-docs

Refactor API docs plugin to work it in the new catalog routing
This commit is contained in:
Fredrik Adelöw
2020-09-07 08:43:56 +02:00
committed by GitHub
29 changed files with 414 additions and 94 deletions
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { Router as GitHubActionsRouter } from '@backstage/plugin-github-actions';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import React from 'react';
import {
AboutCard,
EntityPageLayout,
useEntity,
AboutCard,
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
import { Grid } from '@material-ui/core';
@@ -49,6 +50,11 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
title="Sentry"
element={<SentryRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/api/*"
title="API"
element={<ApiDocsRouter entity={entity} />}
/>
</EntityPageLayout>
);
@@ -8,6 +8,7 @@
"@material-ui/icons": "^4.9.1",
"@backstage/cli": "^{{version}}",
"@backstage/core": "^{{version}}",
"@backstage/plugin-api-docs": "^{{version}}",
"@backstage/plugin-catalog": "^{{version}}",
"@backstage/plugin-register-component": "^{{version}}",
"@backstage/plugin-scaffolder": "^{{version}}",
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { Router as GitHubActionsRouter } from '@backstage/plugin-github-actions';
import React from 'react';
import {
@@ -38,6 +39,11 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
title="CI/CD"
element={<GitHubActionsRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/api/*"
title="API"
element={<ApiDocsRouter entity={entity} />}
/>
</EntityPageLayout>
);
+1
View File
@@ -31,6 +31,7 @@
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"swagger-ui-react": "^3.31.1"
@@ -0,0 +1,47 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ComponentEntity, Entity } from '@backstage/catalog-model';
import { Progress } from '@backstage/core';
import React, { FC } from 'react';
import { Grid } from '@material-ui/core';
import {
ApiDefinitionCard,
useComponentApiEntities,
useComponentApiNames,
} from '../../components';
export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => {
const apiNames = useComponentApiNames(entity as ComponentEntity);
const { apiEntities, loading } = useComponentApiEntities({
entity: entity as ComponentEntity,
});
if (loading) {
return <Progress />;
}
return (
<Grid container spacing={3}>
{apiNames.map(api => (
<Grid item xs={12} key={api}>
<ApiDefinitionCard title={api} apiEntity={apiEntities!.get(api)} />
</Grid>
))}
</Grid>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { EntityPageApi } from './EntityPageApi';
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { Entity } from '@backstage/catalog-model';
import { Route, Routes } from 'react-router';
import { WarningPanel } from '@backstage/core';
import { catalogRoute } from '../routes';
import { EntityPageApi } from './EntityPageApi';
const isPluginApplicableToEntity = (entity: Entity) => {
return ((entity.spec?.implementsApis as string[]) || []).length > 0;
};
export const Router = ({ entity }: { entity: Entity }) =>
// TODO(shmidt-i): move warning to a separate standardized component
!isPluginApplicableToEntity(entity) ? (
<WarningPanel title="API Docs plugin:">
The entity doesn't implement any APIs.
</WarningPanel>
) : (
<Routes>
<Route
path={`/${catalogRoute.path}`}
element={<EntityPageApi entity={entity} />}
/>
)
</Routes>
);
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { Router } from './Router';
@@ -16,7 +16,6 @@
import { Entity } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry, storageApiRef } from '@backstage/core';
// TODO: Circular ref!
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
@@ -15,11 +15,10 @@
*/
import { Content, useApi } from '@backstage/core';
// TODO: Circular ref
import { catalogApiRef } from '@backstage/plugin-catalog';
import React from 'react';
import { useAsync } from 'react-use';
import { ApiCatalogTable } from '../ApiCatalogTable/ApiCatalogTable';
import { ApiCatalogTable } from '../ApiCatalogTable';
import ApiCatalogLayout from './ApiCatalogLayout';
const CatalogPageContents = () => {
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiCatalogPage } from './ApiCatalogPage';
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiCatalogTable } from './ApiCatalogTable';
@@ -14,23 +14,32 @@
* limitations under the License.
*/
import { ApiEntityV1alpha1 } from '@backstage/catalog-model';
import { ApiEntity } from '@backstage/catalog-model';
import { InfoCard } from '@backstage/core';
import React from 'react';
import { ApiDefinitionWidget } from '../ApiDefinitionWidget/ApiDefinitionWidget';
import { ApiDefinitionWidget } from '../ApiDefinitionWidget';
import { Alert } from '@material-ui/lab';
type Props = {
title?: string;
apiEntity: ApiEntityV1alpha1;
apiEntity?: ApiEntity;
};
export const ApiDefinitionCard = ({ title, apiEntity }: Props) => {
const type = apiEntity?.spec?.type || '';
const definition = apiEntity?.spec?.definition || '';
if (!apiEntity) {
return (
<InfoCard title={title}>
<Alert severity="error">Could not fetch the API</Alert>
</InfoCard>
);
}
return (
<InfoCard title={title} subheader={type}>
<ApiDefinitionWidget type={type} definition={definition} />
<InfoCard title={title} subheader={apiEntity.spec.type}>
<ApiDefinitionWidget
type={apiEntity.spec.type}
definition={apiEntity.spec.definition}
/>
</InfoCard>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiDefinitionCard } from './ApiDefinitionCard';
@@ -15,9 +15,9 @@
*/
import React from 'react';
import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget/AsyncApiDefinitionWidget';
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget/OpenApiDefinitionWidget';
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget/PlainApiDefinitionWidget';
import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget';
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
type Props = {
type: string;
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiDefinitionWidget } from './ApiDefinitionWidget';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiEntityV1alpha1, Entity } from '@backstage/catalog-model';
import { ApiEntity, Entity } from '@backstage/catalog-model';
import {
Content,
errorApiRef,
@@ -25,14 +25,13 @@ import {
Progress,
useApi,
} from '@backstage/core';
// TODO: Circular ref
import { catalogApiRef } from '@backstage/plugin-catalog';
import { Box } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import React, { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useAsync } from 'react-use';
import { ApiDefinitionCard } from '../ApiDefinitionCard/ApiDefinitionCard';
import { ApiDefinitionCard } from '../ApiDefinitionCard';
const REDIRECT_DELAY = 1000;
function headerProps(
@@ -125,7 +124,7 @@ export const ApiEntityPage = () => {
{entity && (
<>
<Content>
<ApiDefinitionCard apiEntity={entity as ApiEntityV1alpha1} />
<ApiDefinitionCard apiEntity={entity as ApiEntity} />
</Content>
</>
)}
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiEntityPage } from './ApiEntityPage';
@@ -48,7 +48,7 @@ const useStyles = makeStyles(theme => ({
border: `1px solid ${fade(theme.palette.primary.main, 0.5)}`,
'&:hover': {
textDecoration: 'none',
'&$disabled': {
'&.Mui-disabled': {
backgroundColor: 'transparent',
},
border: `1px solid ${theme.palette.primary.main}`,
@@ -61,7 +61,7 @@ const useStyles = makeStyles(theme => ({
backgroundColor: 'transparent',
},
},
'&$disabled': {
'&.Mui-disabled': {
color: theme.palette.action.disabled,
},
},
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { PlainApiDefinitionWidget } from './PlainApiDefinitionWidget';
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiDefinitionCard } from './ApiDefinitionCard';
export { useComponentApiNames } from './useComponentApiNames';
export { useComponentApiEntities } from './useComponentApiEntities';
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { useAsyncRetry } from 'react-use';
import { errorApiRef, useApi } from '@backstage/core';
import { ApiEntity, ComponentEntity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog';
import { useComponentApiNames } from './useComponentApiNames';
export function useComponentApiEntities({
entity,
}: {
entity: ComponentEntity;
}): {
loading: boolean;
apiEntities?: Map<String, ApiEntity>;
error?: Error;
retry: () => void;
} {
const catalogApi = useApi(catalogApiRef);
const errorApi = useApi(errorApiRef);
const apiNames = useComponentApiNames(entity);
const { loading, value: apiEntities, retry, error } = useAsyncRetry<
Map<string, ApiEntity>
>(async () => {
const resultMap = new Map<string, ApiEntity>();
await Promise.all(
apiNames.map(async name => {
try {
const api = (await catalogApi.getEntityByName({
kind: 'API',
name,
})) as ApiEntity | undefined;
if (api) {
resultMap.set(api.metadata.name, api);
}
} catch (e) {
errorApi.post(e);
}
}),
);
return resultMap;
}, [catalogApi, entity]);
return {
apiEntities,
loading,
error,
retry,
};
}
@@ -0,0 +1,21 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ComponentEntity } from '@backstage/catalog-model';
export const useComponentApiNames = (entity: ComponentEntity) => {
return (entity.spec?.implementsApis as string[]) || [];
};
+1 -1
View File
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { ApiDefinitionCard } from './components/ApiDefinitionCard/ApiDefinitionCard';
export { Router } from './catalog';
export { plugin } from './plugin';
+5
View File
@@ -28,3 +28,8 @@ export const entityRoute = createRouteRef({
path: '/api-docs/:optionalNamespaceAndName/',
title: 'API',
});
export const catalogRoute = createRouteRef({
icon: NoIcon,
path: '',
title: 'API',
});
-1
View File
@@ -23,7 +23,6 @@
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-api-docs": "^0.1.1-alpha.21",
"@backstage/plugin-github-actions": "^0.1.1-alpha.21",
"@backstage/plugin-jenkins": "^0.1.1-alpha.21",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.21",
@@ -1,72 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiEntityV1alpha1, Entity } from '@backstage/catalog-model';
import { Content, Progress, useApi } from '@backstage/core';
import { ApiDefinitionCard } from '@backstage/plugin-api-docs';
import { Grid } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import React, { FC } from 'react';
import { useAsync } from 'react-use';
import { catalogApiRef } from '../..';
export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => {
const catalogApi = useApi(catalogApiRef);
const { value: apiEntities, loading } = useAsync(async () => {
const a = await Promise.all(
((entity?.spec?.implementsApis as string[]) || []).map(api =>
catalogApi.getEntityByName({
kind: 'API',
name: api,
}),
),
);
const b = new Map<string, ApiEntityV1alpha1>();
a.filter(api => !!api).forEach(api => {
b.set(api?.metadata?.name!, api as ApiEntityV1alpha1);
});
return b;
}, [catalogApi, entity]);
return (
<Content>
{loading && <Progress />}
{!loading && (
<Grid container spacing={3}>
{((entity?.spec?.implementsApis as string[]) || []).map(api => {
const apiEntity = apiEntities && apiEntities.get(api);
return (
<Grid item sm={12} key={api}>
{!apiEntity && (
<Alert severity="error">
Error on fetching the API: {api}
</Alert>
)}
{apiEntity && (
<ApiDefinitionCard title={api} apiEntity={apiEntity} />
)}
</Grid>
);
})}
</Grid>
)}
</Content>
);
};