Skip to content

Commit

Permalink
Hide ReadmeCard if backend returns 404 (AxisCommunications#194)
Browse files Browse the repository at this point in the history
* Export isReadmeAvailable function from readme-plugin

* Add changeset

* Update docs

* Update api-report

* Update response error from backend

* Update changeset

* Fix linting problems

* Use ResponseError for all errors

---------

Co-authored-by: Frida Jacobsson <[email protected]>
  • Loading branch information
fridajac and Frida Jacobsson authored Oct 11, 2024
1 parent ebb0f10 commit 0726c03
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 35 deletions.
7 changes: 7 additions & 0 deletions .changeset/kind-avocados-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@axis-backstage/plugin-readme-backend': minor
'@axis-backstage/plugin-readme': minor
'app': minor
---

Created the isReadmeAvailable function that returns false if no README content is found due to 404-error. If it returns false, no ReadmeCard is rendered in EntityPage. Also updated the error response from backend to be a NotFoundError.
12 changes: 8 additions & 4 deletions packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
EntityJiraDashboardContent,
isJiraDashboardAvailable,
} from '@axis-backstage/plugin-jira-dashboard';
import { ReadmeCard } from '@axis-backstage/plugin-readme';
import { ReadmeCard, isReadmeAvailable } from '@axis-backstage/plugin-readme';
import {
isStatuspageAvailable,
StatuspageEntityCard,
Expand Down Expand Up @@ -115,9 +115,13 @@ const overviewContent = (
<Grid md={6} xs={12}>
<EntityLinksCard />
</Grid>
<Grid md={6} xs={12}>
<ReadmeCard />
</Grid>
<EntitySwitch>
<EntitySwitch.Case if={isReadmeAvailable}>
<Grid md={6} xs={12}>
<ReadmeCard maxHeight={350} />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
<Grid md={6} xs={12}>
<EntityCatalogGraphCard height={400} />
</Grid>
Expand Down
1 change: 1 addition & 0 deletions plugins/readme-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@backstage/backend-plugin-api": "^0.8.0",
"@backstage/catalog-client": "^1.6.6",
"@backstage/catalog-model": "^1.6.0",
"@backstage/errors": "^1.2.4",
"@backstage/integration": "^1.14.0",
"@types/express": "*",
"express": "^4.17.1",
Expand Down
16 changes: 6 additions & 10 deletions plugins/readme-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { CatalogClient } from '@backstage/catalog-client';
import { isError, NotFoundError } from '@backstage/errors';
import express from 'express';
import Router from 'express-promise-router';
import { isSymLink } from '../lib';
Expand Down Expand Up @@ -122,11 +123,9 @@ export async function createRouter(
const source = getEntitySourceLocation(entity);

if (!source || source.type !== 'url') {
logger.info(`Not valid location for ${source.target}`);
response.status(404).json({
error: `Not valid location for ${source.target}`,
});
return;
const errorMessage = `Not valid location for ${source.target}`;
logger.info(errorMessage);
throw new NotFoundError(errorMessage);
}
const integration = integrations.byUrl(source.target);

Expand Down Expand Up @@ -170,8 +169,7 @@ export async function createRouter(
response.send(content);
return;
} catch (error: unknown) {
if (error instanceof Error && error.name === 'NotFoundError') {
// Try the next readme type
if (isError(error) && error.name === 'NotFoundError') {
continue;
} else {
response.status(500).json({
Expand All @@ -182,9 +180,7 @@ export async function createRouter(
}
}
logger.info(`Readme not found for ${entityRef}`);
response.status(404).json({
error: 'Readme not found.',
});
throw new NotFoundError('Readme could not be found');
});

const middleware = MiddlewareFactory.create({ logger, config });
Expand Down
19 changes: 19 additions & 0 deletions plugins/readme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ const overviewContent = (
)
```

If you wish to only render the ReadmeCard if a README file can be found for the entity, you can use the exported function **isReadmeAvailable**. See example below:

```tsx
import { ReadmeCard, isReadmeAvailable } from '@axis-backstage/plugin-readme';

const defaultEntityPage = (
...
<EntitySwitch>
<EntitySwitch.Case if={isReadmeAvailable}>
<Grid md={6} xs={12}>
<ReadmeCard maxHeight={350} />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
...
)
```

To use `ReadmeCard` in a seperate page with full height:

```tsx
Expand All @@ -59,6 +77,7 @@ const defaultEntityPage = (
<ReadmeCard variant="fullHeight" />
</EntityLayout.Route>
...
)
```

## Layout
Expand Down
10 changes: 10 additions & 0 deletions plugins/readme/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
```ts
/// <reference types="react" />

import { ApiHolder } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { InfoCardVariants } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';

// @public
export const isReadmeAvailable: (
entity: Entity,
context: {
apis: ApiHolder;
},
) => Promise<boolean>;

// @public
export const ReadmeCard: (props: ReadmeCardProps) => JSX_2.Element;

Expand Down
1 change: 1 addition & 0 deletions plugins/readme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@backstage/catalog-model": "^1.6.0",
"@backstage/core-components": "^0.14.10",
"@backstage/core-plugin-api": "^1.9.3",
"@backstage/errors": "^1.2.4",
"@backstage/plugin-catalog-react": "^1.12.3",
"@backstage/theme": "^0.5.6",
"@mui/icons-material": "^5.15.7",
Expand Down
46 changes: 40 additions & 6 deletions plugins/readme/src/api/ReadmeClient.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
import {
ApiHolder,
DiscoveryApi,
FetchApi,
IdentityApi,
} from '@backstage/core-plugin-api';
import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model';
import { ReadmeApi } from './ReadmeApi';
import {
DEFAULT_NAMESPACE,
Entity,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { ResponseError } from '@backstage/errors';
import { ReadmeApi, readmeApiRef } from './ReadmeApi';

/**
* Checks if a README is available for the given entity by making a request to the backend.
If the backend returns a 404 NotFound error, it indicates that no README is available for the entity.
@param entity - The entity for which to check the README availability.
@param context - The context providing access to the API holder.
@returns A promise that resolves to true if the README is available, or false if not found (404).
* @public
*/
export const isReadmeAvailable = async (
entity: Entity,
context: { apis: ApiHolder },
): Promise<boolean> => {
const readmeClient = context.apis.get(readmeApiRef);

if (readmeClient === undefined) {
return false;
}

try {
await readmeClient.getReadmeContent(stringifyEntityRef(entity));
} catch (error) {
if (error instanceof ResponseError && error.statusCode === 404) {
return false;
}
}
return true;
};

export class ReadmeClient implements ReadmeApi {
private readonly discoveryApi: DiscoveryApi;
Expand Down Expand Up @@ -36,15 +71,14 @@ export class ReadmeClient implements ReadmeApi {
headers: { Authorization: `Bearer ${token}` },
},
);

if (resp.ok) {
return [
await resp.text(),
resp.headers.get('Content-Type') || 'text/plain',
];
}
if (resp.status === 404) {
throw new Error('404');
}
throw new Error(`${resp.status}: ${resp.statusText}`);

throw await ResponseError.fromResponse(resp);
}
}
36 changes: 21 additions & 15 deletions plugins/readme/src/components/FetchComponent/FetchComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
Link,
MarkdownContent,
Progress,
ResponseErrorPanel,
} from '@backstage/core-components';
import useAsync from 'react-use/lib/useAsync';
import { readmeApiRef } from '../../api/ReadmeApi';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { getEntitySourceLocation } from '@backstage/catalog-model';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { ResponseError } from '@backstage/errors';

export const FetchComponent = () => {
const { entity } = useEntity();
Expand All @@ -40,21 +42,25 @@ export const FetchComponent = () => {
if (loading) {
return <Progress />;
}
if (error?.message === '404') {
return (
<Box>
<Typography pb={2} variant="body2">
No README.md file found at source location:{' '}
{location && <strong>{location}</strong>}
</Typography>
<Typography variant="body2">
Need help? Go to our{' '}
<Link to="https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md">
documentation
</Link>
</Typography>
</Box>
);

if (error instanceof ResponseError) {
if (error.statusCode === 404) {
return (
<Box>
<Typography pb={2} variant="body2">
No README.md file found at source location:{' '}
{location && <strong>{location}</strong>}
</Typography>
<Typography variant="body2">
Need help? Go to our{' '}
<Link to="https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md">
documentation
</Link>
</Typography>
</Box>
);
}
return <ResponseErrorPanel error={error} />;
}

if (error) {
Expand Down
1 change: 1 addition & 0 deletions plugins/readme/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

export { readmePlugin, ReadmeCard } from './plugin';
export type { ReadmeCardProps } from './components/ReadmeCard';
export { isReadmeAvailable } from './api/ReadmeClient';
2 changes: 2 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,7 @@ __metadata:
"@backstage/catalog-client": "npm:^1.6.6"
"@backstage/catalog-model": "npm:^1.6.0"
"@backstage/cli": "npm:^0.27.0"
"@backstage/errors": "npm:^1.2.4"
"@backstage/integration": "npm:^1.14.0"
"@types/express": "npm:*"
"@types/supertest": "npm:^2.0.12"
Expand All @@ -1552,6 +1553,7 @@ __metadata:
"@backstage/core-components": "npm:^0.14.10"
"@backstage/core-plugin-api": "npm:^1.9.3"
"@backstage/dev-utils": "npm:^1.0.37"
"@backstage/errors": "npm:^1.2.4"
"@backstage/plugin-catalog-react": "npm:^1.12.3"
"@backstage/test-utils": "npm:^1.5.10"
"@backstage/theme": "npm:^0.5.6"
Expand Down

0 comments on commit 0726c03

Please sign in to comment.