diff --git a/.changeset/grumpy-countries-flow.md b/.changeset/grumpy-countries-flow.md new file mode 100644 index 0000000..6d6e879 --- /dev/null +++ b/.changeset/grumpy-countries-flow.md @@ -0,0 +1,5 @@ +--- +'@dweber019/backstage-plugin-api-docs-spectral-linter': minor +--- + +Add links to external documentation when that is available. Render rule descriptions as markdown. diff --git a/plugins/api-docs-spectral-linter/dev/index.tsx b/plugins/api-docs-spectral-linter/dev/index.tsx index da7125f..e9b5aeb 100644 --- a/plugins/api-docs-spectral-linter/dev/index.tsx +++ b/plugins/api-docs-spectral-linter/dev/index.tsx @@ -16,6 +16,8 @@ import openapiApiEntity from './openapi-example-api.yaml'; import openapiZalandoApiEntity from './openapi-zalando-example-api.yaml'; // @ts-ignore import openapiBaloiseApiEntity from './openapi-baloise-example-api.yaml'; +// @ts-ignore +import openapiOwaspApiEntity from './openapi-owasp-example-api.yaml'; const mockConfig = new MockConfigApi({ spectralLinter: { @@ -69,4 +71,13 @@ createDevApp() title: 'Open API - Baloise', path: '/open-api-baloise', }) + .addPage({ + element: ( + + + + ), + title: 'Open API - OWASP', + path: '/open-api-owasp', + }) .render(); diff --git a/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml b/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml new file mode 100644 index 0000000..d163965 --- /dev/null +++ b/plugins/api-docs-spectral-linter/dev/openapi-owasp-example-api.yaml @@ -0,0 +1,126 @@ +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: petstore-owasp-rules + description: The petstore API + tags: + - store + - rest + annotations: + backstage.io/spectral-ruleset-url: https://unpkg.com/@stoplight/spectral-owasp-ruleset@2.0.1/dist/ruleset.mjs +spec: + type: openapi + lifecycle: experimental + owner: team-c + definition: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + servers: + - url: http://petstore.swagger.io/v1 + paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/plugins/api-docs-spectral-linter/src/api/LinterClient.ts b/plugins/api-docs-spectral-linter/src/api/LinterClient.ts index 8f19bb1..e158cad 100644 --- a/plugins/api-docs-spectral-linter/src/api/LinterClient.ts +++ b/plugins/api-docs-spectral-linter/src/api/LinterClient.ts @@ -103,6 +103,8 @@ export class LinterClient implements LinterApi { severity: diagnosticItem.severity, path: diagnosticItem.path.map(item => item.toString()), code: diagnosticItem.code, + ruleDocumentationUrl: ruleDocumentationUrl(spectral, diagnosticItem.code), + ruleDescription: ruleDescription(spectral, diagnosticItem.code) })), }; } @@ -124,3 +126,11 @@ export class LinterClient implements LinterApi { return isApiDocsSpectralLinterAvailable(entity); } } + +function ruleDocumentationUrl(spectral: Spectral, code: string | number): string | undefined { + return spectral.ruleset?.rules[code].documentationUrl || undefined +} + +function ruleDescription(spectral: Spectral, code: string | number): string | undefined { + return spectral.ruleset?.rules[code].description || undefined +} diff --git a/plugins/api-docs-spectral-linter/src/api/types.ts b/plugins/api-docs-spectral-linter/src/api/types.ts index c87c212..5287d6e 100644 --- a/plugins/api-docs-spectral-linter/src/api/types.ts +++ b/plugins/api-docs-spectral-linter/src/api/types.ts @@ -37,6 +37,10 @@ export type LinterResultData = { */ severity: number; + ruleDocumentationUrl?: string; + + ruleDescription?: string; + /** * The path in content. */ diff --git a/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx b/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx index 0e7f1be..76aac29 100644 --- a/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx +++ b/plugins/api-docs-spectral-linter/src/components/EntityApiDocsSpectralLinterContent/EntityApiDocsSpectralLinterContent.tsx @@ -18,6 +18,7 @@ import React, { useState } from 'react'; import { CodeSnippet, InfoCard, + MarkdownContent, Progress, WarningPanel, } from '@backstage/core-components'; @@ -113,41 +114,50 @@ export const EntityApiDocsSpectralLinterContent = () => { value.data.map((ruleResult, idx) => ( - - - {ruleResult.message} ({ruleResult.code}) - - {`alert${idx}` === expanded ? ( - - ) : ( - - )} - - - {`alert${idx}` === expanded && ( - - )} - + + + {ruleResult.message} ({ruleResult.code}) + + {`alert${idx}` === expanded ? ( + + ) : ( + + )} + + + {`alert${idx}` === expanded && ( + + + + + )} + ))