mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-23 09:03:44 +03:00
feat: add support x-badges (#2605)
* feat: add support x-badges Co-authored-by: Max Mueller <maxmueller@eaton.com> * chore: try to fix e2e tests * chore: try to fix e2e tests part 2 * Update docs/redoc-vendor-extensions.md --------- Co-authored-by: Max Mueller <maxmueller@eaton.com> Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
This commit is contained in:
parent
1cceed4b47
commit
64f18779e5
|
@ -49,7 +49,7 @@ Redoc is provided as a CLI tool (also distributed as a Docker image), HTML tag,
|
||||||
If you have Node installed, quickly generate documentation using `npx`:
|
If you have Node installed, quickly generate documentation using `npx`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @redocly/cli build-docs openapi.yaml
|
npx @redocly/cli build-docs openapi.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
The tool outputs by default to a file named `redoc-static.html` that you can open in your browser.
|
The tool outputs by default to a file named `redoc-static.html` that you can open in your browser.
|
||||||
|
@ -116,6 +116,7 @@ Redoc uses the following [specification extensions](https://redocly.com/docs/api
|
||||||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for tags that refer to non-navigation properties like Pagination, Rate-Limits, etc
|
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for tags that refer to non-navigation properties like Pagination, Rate-Limits, etc
|
||||||
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
|
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
|
||||||
|
* [`x-badges`](docs/redoc-vendor-extensions.md#x-badges) - specify operation badges
|
||||||
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
||||||
* [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable
|
* [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable
|
||||||
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
||||||
|
|
|
@ -22,6 +22,10 @@ paths:
|
||||||
operationId: getMuseumHours
|
operationId: getMuseumHours
|
||||||
tags:
|
tags:
|
||||||
- Operations
|
- Operations
|
||||||
|
x-badges:
|
||||||
|
- name: 'Beta'
|
||||||
|
position: before
|
||||||
|
color: purple
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/StartDate'
|
- $ref: '#/components/parameters/StartDate'
|
||||||
- $ref: '#/components/parameters/PaginationPage'
|
- $ref: '#/components/parameters/PaginationPage'
|
||||||
|
@ -64,6 +68,9 @@ paths:
|
||||||
summary: Create special event
|
summary: Create special event
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
|
x-badges:
|
||||||
|
- name: 'Alpha'
|
||||||
|
color: purple
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
|
@ -92,6 +99,8 @@ paths:
|
||||||
description: Return a list of upcoming special events at the museum.
|
description: Return a list of upcoming special events at the museum.
|
||||||
security: []
|
security: []
|
||||||
operationId: listSpecialEvents
|
operationId: listSpecialEvents
|
||||||
|
x-badges:
|
||||||
|
- name: 'Gamma'
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -106,6 +106,10 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
|
x-badges:
|
||||||
|
- name: 'Beta'
|
||||||
|
position: before
|
||||||
|
color: purple
|
||||||
summary: Add a new pet to the store
|
summary: Add a new pet to the store
|
||||||
description: Add new pet to the store inventory.
|
description: Add new pet to the store inventory.
|
||||||
operationId: addPet
|
operationId: addPet
|
||||||
|
@ -150,6 +154,9 @@ paths:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
|
x-badges:
|
||||||
|
- name: 'Alpha'
|
||||||
|
color: purple
|
||||||
summary: Update an existing pet
|
summary: Update an existing pet
|
||||||
description: ''
|
description: ''
|
||||||
operationId: updatePet
|
operationId: updatePet
|
||||||
|
@ -183,6 +190,8 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
|
x-badges:
|
||||||
|
- name: 'Gamma'
|
||||||
summary: Find pet by ID
|
summary: Find pet by ID
|
||||||
description: Returns a single pet
|
description: Returns a single pet
|
||||||
operationId: getPetById
|
operationId: getPetById
|
||||||
|
|
|
@ -252,6 +252,11 @@ lang: JavaScript
|
||||||
source: console.log('Hello World');
|
source: console.log('Hello World');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### x-badges
|
||||||
|
| Field Name | Type | Description |
|
||||||
|
| :------------- | :------: | :---------- |
|
||||||
|
| x-badges | [[Badge Object](https://redocly.com/docs/realm/author/reference/openapi-extensions/x-badges#badge-object)] | A list of badges associated with the operation |
|
||||||
|
|
||||||
## Parameter Object
|
## Parameter Object
|
||||||
Extends the OpenAPI [Parameter Object](https://redocly.com/docs/openapi-visual-reference/parameter/)
|
Extends the OpenAPI [Parameter Object](https://redocly.com/docs/openapi-visual-reference/parameter/)
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,31 @@ describe('Menu', () => {
|
||||||
cy.location('hash').should('equal', '#schema/Cat');
|
cy.location('hash').should('equal', '#schema/Cat');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should contains badge schema from x-badges', () => {
|
||||||
|
cy.contains('h2', 'Add a new pet to the store').scrollIntoView();
|
||||||
|
|
||||||
|
cy.contains('h2 > span', 'Beta')
|
||||||
|
.scrollIntoView()
|
||||||
|
.wait(100)
|
||||||
|
.get('[role=menuitem] > label.active')
|
||||||
|
.children('span[type="badge"]')
|
||||||
|
.should('have.text', 'Beta');
|
||||||
|
|
||||||
|
cy.contains('h2 > span', 'Alpha')
|
||||||
|
.scrollIntoView()
|
||||||
|
.wait(100)
|
||||||
|
.get('[role=menuitem] > label.active')
|
||||||
|
.children('span[type="badge"]')
|
||||||
|
.should('have.text', 'Alpha');
|
||||||
|
|
||||||
|
cy.contains('h2 > span', 'Gamma')
|
||||||
|
.scrollIntoView()
|
||||||
|
.wait(100)
|
||||||
|
.get('[role=menuitem] > label.active')
|
||||||
|
.children('span[type="badge"]')
|
||||||
|
.should('have.text', 'Gamma');
|
||||||
|
});
|
||||||
|
|
||||||
it('should contains Cat schema in Pet using x-tags', () => {
|
it('should contains Cat schema in Pet using x-tags', () => {
|
||||||
cy.contains('[role=menuitem] > label.-depth1', 'pet').click({ force: true });
|
cy.contains('[role=menuitem] > label.-depth1', 'pet').click({ force: true });
|
||||||
cy.location('hash').should('equal', '#tag/pet');
|
cy.location('hash').should('equal', '#tag/pet');
|
||||||
|
|
|
@ -47,11 +47,11 @@ export const ShelfIcon = styled(IntShelfIcon)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Badge = styled.span<{ type: string }>`
|
export const Badge = styled.span<{ type: string; color?: string }>`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: ${props => props.theme.colors[props.type].main};
|
background-color: ${props => props.color || props.theme.colors[props.type].main};
|
||||||
color: ${props => props.theme.colors[props.type].contrastText};
|
color: ${props => props.theme.colors[props.type].contrastText};
|
||||||
font-size: ${props => props.theme.typography.code.fontSize};
|
font-size: ${props => props.theme.typography.code.fontSize};
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -28,9 +28,20 @@ export interface OperationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Operation = observer(({ operation }: OperationProps): JSX.Element => {
|
export const Operation = observer(({ operation }: OperationProps): JSX.Element => {
|
||||||
const { name: summary, description, deprecated, externalDocs, isWebhook, httpVerb } = operation;
|
const {
|
||||||
|
name: summary,
|
||||||
|
description,
|
||||||
|
deprecated,
|
||||||
|
externalDocs,
|
||||||
|
isWebhook,
|
||||||
|
httpVerb,
|
||||||
|
badges,
|
||||||
|
} = operation;
|
||||||
const hasDescription = !!(description || externalDocs);
|
const hasDescription = !!(description || externalDocs);
|
||||||
const { showWebhookVerb } = React.useContext(OptionsContext);
|
const { showWebhookVerb } = React.useContext(OptionsContext);
|
||||||
|
const badgesBefore = badges.filter(({ position }) => position === 'before');
|
||||||
|
const badgesAfter = badges.filter(({ position }) => position === 'after');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<OptionsContext.Consumer>
|
||||||
{options => (
|
{options => (
|
||||||
|
@ -38,6 +49,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
|
||||||
<MiddlePanel>
|
<MiddlePanel>
|
||||||
<H2>
|
<H2>
|
||||||
<ShareLink to={operation.id} />
|
<ShareLink to={operation.id} />
|
||||||
|
{badgesBefore.map(({ name, color }) => (
|
||||||
|
<Badge type="primary" key={name} color={color}>
|
||||||
|
{name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||||
{isWebhook && (
|
{isWebhook && (
|
||||||
<Badge type="primary">
|
<Badge type="primary">
|
||||||
|
@ -45,6 +61,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
|
||||||
Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()}
|
Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{badgesAfter.map(({ name, color }) => (
|
||||||
|
<Badge type="primary" key={name} color={color}>
|
||||||
|
{name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
</H2>
|
</H2>
|
||||||
{options.pathInMiddlePanel && !isWebhook && (
|
{options.pathInMiddlePanel && !isWebhook && (
|
||||||
<Endpoint operation={operation} inverted={true} />
|
<Endpoint operation={operation} inverted={true} />
|
||||||
|
|
|
@ -101,6 +101,12 @@ export const OperationMenuItemContent = observer((props: OperationMenuItemConten
|
||||||
$deprecated={item.deprecated}
|
$deprecated={item.deprecated}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
|
{item.badges &&
|
||||||
|
item.badges?.map(({ name, color }) => (
|
||||||
|
<OperationBadge type="badge" color={color} key={name}>
|
||||||
|
{name}
|
||||||
|
</OperationBadge>
|
||||||
|
))}
|
||||||
{item.isWebhook ? (
|
{item.isWebhook ? (
|
||||||
<OperationBadge type="hook">
|
<OperationBadge type="hook">
|
||||||
{showWebhookVerb ? item.httpVerb : l('webhook')}
|
{showWebhookVerb ? item.httpVerb : l('webhook')}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import { darken } from 'polished';
|
||||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||||
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
|
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
|
||||||
|
|
||||||
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
export const OperationBadge = styled.span.attrs((props: { type: string; color?: string }) => ({
|
||||||
className: `operation-type ${props.type}`,
|
className: `operation-type ${props.type}`,
|
||||||
}))<{ type: string }>`
|
}))<{ type: string; color?: string }>`
|
||||||
width: 9ex;
|
width: 9ex;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: ${props => props.theme.typography.code.fontSize};
|
height: ${props => props.theme.typography.code.fontSize};
|
||||||
line-height: ${props => props.theme.typography.code.fontSize};
|
line-height: ${props => props.theme.typography.code.fontSize};
|
||||||
background-color: #333;
|
background-color: ${props => props.color || '#333'};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 6px 4px;
|
background-position: 6px 4px;
|
||||||
|
|
|
@ -20,7 +20,12 @@ import { RequestBodyModel } from './RequestBody';
|
||||||
import { ResponseModel } from './Response';
|
import { ResponseModel } from './Response';
|
||||||
import { SideNavStyleEnum } from '../types';
|
import { SideNavStyleEnum } from '../types';
|
||||||
|
|
||||||
import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
import type {
|
||||||
|
OpenAPIExternalDocumentation,
|
||||||
|
OpenAPIServer,
|
||||||
|
OpenAPIXBadges,
|
||||||
|
OpenAPIXCodeSample,
|
||||||
|
} from '../../types';
|
||||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import type { MediaContentModel } from './MediaContent';
|
import type { MediaContentModel } from './MediaContent';
|
||||||
|
@ -71,6 +76,7 @@ export class OperationModel implements IMenuItem {
|
||||||
operationId?: string;
|
operationId?: string;
|
||||||
operationHash?: string;
|
operationHash?: string;
|
||||||
httpVerb: string;
|
httpVerb: string;
|
||||||
|
badges: OpenAPIXBadges[];
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
path: string;
|
path: string;
|
||||||
servers: OpenAPIServer[];
|
servers: OpenAPIServer[];
|
||||||
|
@ -112,6 +118,12 @@ export class OperationModel implements IMenuItem {
|
||||||
: options.sideNavStyle === SideNavStyleEnum.PathOnly
|
: options.sideNavStyle === SideNavStyleEnum.PathOnly
|
||||||
? this.path
|
? this.path
|
||||||
: this.name;
|
: this.name;
|
||||||
|
this.badges =
|
||||||
|
operationSpec['x-badges']?.map(({ name, color, position }) => ({
|
||||||
|
name,
|
||||||
|
color: color,
|
||||||
|
position: position || 'after',
|
||||||
|
})) || [];
|
||||||
|
|
||||||
if (this.isCallback) {
|
if (this.isCallback) {
|
||||||
// NOTE: Callbacks by default should not inherit the specification's global `security` definition.
|
// NOTE: Callbacks by default should not inherit the specification's global `security` definition.
|
||||||
|
|
|
@ -70,6 +70,12 @@ export interface OpenAPIXCodeSample {
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenAPIXBadges {
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
position?: 'before' | 'after';
|
||||||
|
}
|
||||||
|
|
||||||
export interface OpenAPIOperation {
|
export interface OpenAPIOperation {
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
summary?: string;
|
summary?: string;
|
||||||
|
@ -85,6 +91,7 @@ export interface OpenAPIOperation {
|
||||||
servers?: OpenAPIServer[];
|
servers?: OpenAPIServer[];
|
||||||
'x-codeSamples'?: OpenAPIXCodeSample[];
|
'x-codeSamples'?: OpenAPIXCodeSample[];
|
||||||
'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated
|
'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated
|
||||||
|
'x-badges'?: OpenAPIXBadges[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenAPIParameter {
|
export interface OpenAPIParameter {
|
||||||
|
|
|
@ -581,6 +581,13 @@ and standard method from web, mobile and desktop applications.
|
||||||
"tags": [
|
"tags": [
|
||||||
"pet",
|
"pet",
|
||||||
],
|
],
|
||||||
|
"x-badges": [
|
||||||
|
{
|
||||||
|
"color": "purple",
|
||||||
|
"name": "Beta",
|
||||||
|
"position": "before",
|
||||||
|
},
|
||||||
|
],
|
||||||
"x-codeSamples": [
|
"x-codeSamples": [
|
||||||
{
|
{
|
||||||
"lang": "C#",
|
"lang": "C#",
|
||||||
|
@ -645,6 +652,12 @@ try {
|
||||||
"tags": [
|
"tags": [
|
||||||
"pet",
|
"pet",
|
||||||
],
|
],
|
||||||
|
"x-badges": [
|
||||||
|
{
|
||||||
|
"color": "purple",
|
||||||
|
"name": "Alpha",
|
||||||
|
},
|
||||||
|
],
|
||||||
"x-codeSamples": [
|
"x-codeSamples": [
|
||||||
{
|
{
|
||||||
"lang": "PHP",
|
"lang": "PHP",
|
||||||
|
@ -883,6 +896,11 @@ try {
|
||||||
"tags": [
|
"tags": [
|
||||||
"pet",
|
"pet",
|
||||||
],
|
],
|
||||||
|
"x-badges": [
|
||||||
|
{
|
||||||
|
"name": "Gamma",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|
|
@ -660,6 +660,7 @@ export function isRedocExtension(key: string): boolean {
|
||||||
'x-servers': true,
|
'x-servers': true,
|
||||||
'x-tagGroups': true,
|
'x-tagGroups': true,
|
||||||
'x-traitTag': true,
|
'x-traitTag': true,
|
||||||
|
'x-badges': true,
|
||||||
'x-additionalPropertiesName': true,
|
'x-additionalPropertiesName': true,
|
||||||
'x-explicitMappingOnly': true,
|
'x-explicitMappingOnly': true,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user