diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 9eb43b05..bc7d797d 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -530,18 +530,19 @@ paths: example: AAA-123-BBB-456 callbacks: orderInProgress: - '/{$request.body#/callbackUrl}?event={$request.body#/eventName}': + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': servers: - - url: //petstore.swagger.io/v2 - description: Default server callback - - url: //petstore.swagger.io/sandbox - description: Sandbox server callback + - url: //callback-url.path-level/v1 + description: Path level server 1 + - url: //callback-url.path-level/v2 + description: Path level server 2 post: - description: A callback triggered every time an Order is updated status to "inProgress" + summary: Order in Progress (Summary) + description: A callback triggered every time an Order is updated status to "inProgress" (Description) requestBody: content: application/json: - schema: + schema: type: object properties: orderId: @@ -613,7 +614,12 @@ paths: var_dump($e->getErrors()); } put: - description: A Put callback triggered + description: Order in Progress (Only Description) + servers: + - url: //callback-url.operation-level/v1 + description: Operation level server 1 (Path override) + - url: //callback-url.operation-level/v2 + description: Operation level server 2 (Path override) requestBody: content: application/json: @@ -656,9 +662,16 @@ paths: type: string example: '123' orderShipped: - '/{$request.body#/callbackUrl}?event={$request.body#/eventName}': + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': post: - description: A callback triggered every time an Order is shipped + description: | + Very long description + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + culpa qui officia deserunt mollit anim id est laborum. requestBody: content: application/json: @@ -680,8 +693,10 @@ paths: '200': description: Callback successfully processed and no retries will be performed orderDelivered: - '/{$request.body#/callbackUrl}?event={$request.body#/eventName}': + 'http://notificationServer.com?url={$request.body#/callbackUrl}&event={$request.body#/eventName}': post: + deprecated: true + summary: Order delivered description: A callback triggered every time an Order is delivered to the recipient requestBody: content: diff --git a/src/components/Callbacks/Callback.tsx b/src/components/Callbacks/Callback.tsx index bc4c2ace..f99232f1 100644 --- a/src/components/Callbacks/Callback.tsx +++ b/src/components/Callbacks/Callback.tsx @@ -6,21 +6,23 @@ import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.el import { CallbackDetails } from './CallbackDetails'; @observer +// TODO: rename to Callback export class CallbackView extends React.Component<{ callbackOperation: OperationModel }> { toggle = () => { this.props.callbackOperation.toggle(); }; render() { - const { name, description, expanded } = this.props.callbackOperation; + const { name, expanded, httpVerb, deprecated } = this.props.callbackOperation; return ( <> {expanded && ( diff --git a/src/components/Callbacks/CallbackDetails.tsx b/src/components/Callbacks/CallbackDetails.tsx index 4f3ceaa8..e955ab3d 100644 --- a/src/components/Callbacks/CallbackDetails.tsx +++ b/src/components/Callbacks/CallbackDetails.tsx @@ -3,14 +3,28 @@ import * as React from 'react'; import { OperationModel } from '../../services/models'; import { OperationItem } from '../ContentItems/ContentItems'; import { Endpoint } from '../Endpoint/Endpoint'; +import { Markdown } from '../Markdown/Markdown'; +import styled from '../../styled-components'; export class CallbackDetails extends React.PureComponent<{ callbackOperation: OperationModel }> { render() { + const description = this.props.callbackOperation.description; + return (
- + {description && ( + + + + )} +
); } } + +const CallbackDescription = styled.div` + margin-top: 10px; + margin-bottom: 20px; +`; diff --git a/src/components/Callbacks/CallbackTitle.tsx b/src/components/Callbacks/CallbackTitle.tsx index b72fc456..8121d248 100644 --- a/src/components/Callbacks/CallbackTitle.tsx +++ b/src/components/Callbacks/CallbackTitle.tsx @@ -1,25 +1,46 @@ import * as React from 'react'; import { ShelfIcon } from '../../common-elements'; -import { Markdown } from '../Markdown/Markdown'; +import { OperationBadge } from '../SideMenu/styled.elements'; +import { shortenHTTPVerb } from '../../utils/openapi'; +import styled from '../../styled-components'; +import { Badge } from '../../common-elements/'; +import { l } from '../../services/Labels'; export interface CallbackTitleProps { name: string; - description?: string; opened?: boolean; className?: string; onClick?: () => void; + httpVerb: string; + deprecated?: boolean; } export class CallbackTitle extends React.PureComponent { render() { - const { name, description, opened, className, onClick } = this.props; + const { name, opened, className, onClick, httpVerb, deprecated } = this.props; return ( -
+ + {shortenHTTPVerb(httpVerb)} - {name} - {description && } -
+ {name} + {deprecated ? {l('deprecated')} : null} + ); } } + +const CallbackTitleWrapper = styled.div` + & > * { + vertical-align: middle; + } +`; + +const CallbackNameStyled = styled.span<{ deprecated?: boolean }>` + text-decoration: ${props => (props.deprecated ? 'line-through' : 'none')}; + margin-right: 8px; +`; + +const OperationBadgeStyled = styled(OperationBadge)` + margin: 0px 5px 0px 0px; +`; diff --git a/src/components/CallbacksSwitch/CallbacksSwitch.tsx b/src/components/CallbacksSwitch/CallbacksSwitch.tsx index 2b0a2b23..8c5b1da8 100644 --- a/src/components/CallbacksSwitch/CallbacksSwitch.tsx +++ b/src/components/CallbacksSwitch/CallbacksSwitch.tsx @@ -43,7 +43,7 @@ export class CallbacksSwitch extends React.Component { return { - label: `[${callback.httpVerb.toUpperCase()}] ${callback.name}`, + label: `${callback.httpVerb.toUpperCase()}: ${callback.name}`, value: idx.toString(), }; }); diff --git a/src/components/Endpoint/Endpoint.tsx b/src/components/Endpoint/Endpoint.tsx index 280a8582..e59ceff5 100644 --- a/src/components/Endpoint/Endpoint.tsx +++ b/src/components/Endpoint/Endpoint.tsx @@ -21,6 +21,7 @@ export interface EndpointProps { hideHostname?: boolean; inverted?: boolean; + compact?: boolean; } export interface EndpointState { @@ -49,7 +50,9 @@ export class Endpoint extends React.Component { {options => ( - {operation.httpVerb}{' '} + + {operation.httpVerb} + {operation.path} ({ +export const HttpVerb = styled.span.attrs((props: { type: string; compact?: boolean }) => ({ className: `http-verb ${props.type}`, -}))<{ type: string }>` - font-size: 0.929em; - line-height: 20px; - background-color: ${(props: any) => props.theme.colors.http[props.type] || '#999999'}; +}))<{ type: string; compact?: boolean }>` + font-size: ${props => (props.compact ? '0.8em' : '0.929em')}; + line-height: ${props => (props.compact ? '18px' : '20px')}; + background-color: ${props => props.theme.colors.http[props.type] || '#999999'}; color: #ffffff; - padding: 3px 10px; + padding: ${props => (props.compact ? '2px 8px' : '3px 10px')}; text-transform: uppercase; font-family: ${props => props.theme.typography.headings.fontFamily}; margin: 0; @@ -59,7 +59,6 @@ export const ServersOverlay = styled.div<{ expanded: boolean }>` border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; transition: all 0.25s ease; - ${props => (props.expanded ? '' : 'transform: translateY(-50%) scaleY(0);')} `; diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 402affd0..89ec56da 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements'; import { ShareLink } from '../../common-elements/linkify'; import { OperationModel as OperationType } from '../../services/models'; -import styled from '../../styled-components'; +import styled, { media } from '../../styled-components'; import { CallbacksList } from '../Callbacks'; import { CallbackSamples } from '../CallbackSamples/CallbackSamples'; import { Endpoint } from '../Endpoint/Endpoint'; @@ -18,22 +18,6 @@ import { ResponsesList } from '../Responses/ResponsesList'; import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement'; -const CallbackMiddlePanel = styled(MiddlePanel)` - width: 100%; - padding: 0; -`; - -const OperationRow = styled(Row)` - backface-visibility: hidden; - contain: content; - - overflow: hidden; -`; - -const Description = styled.div` - margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px; -`; - export interface OperationProps { operation: OperationType; } @@ -70,7 +54,7 @@ export class Operation extends React.Component { - + {!operation.isCallback && ( @@ -89,3 +73,22 @@ export class Operation extends React.Component { ); } } + +const CallbackMiddlePanel = styled(MiddlePanel)` + width: 100%; + padding: 0; + ${() => media.lessThan('medium', true)` + padding: 0 + `}; +`; + +const OperationRow = styled(Row)` + backface-visibility: hidden; + contain: content; + + overflow: hidden; +`; + +const Description = styled.div` + margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px; +`; diff --git a/src/components/Responses/ResponsesList.tsx b/src/components/Responses/ResponsesList.tsx index da465489..cab796fe 100644 --- a/src/components/Responses/ResponsesList.tsx +++ b/src/components/Responses/ResponsesList.tsx @@ -13,11 +13,12 @@ const ResponsesHeader = styled.h3` export interface ResponseListProps { responses: ResponseModel[]; + isCallback?: boolean; } export class ResponsesList extends React.PureComponent { render() { - const { responses } = this.props; + const { responses, isCallback } = this.props; if (!responses || responses.length === 0) { return null; @@ -25,7 +26,7 @@ export class ResponsesList extends React.PureComponent { return (
- Responses + {isCallback ? 'Callback responses' : 'Responses'} {responses.map(response => { return ; })} diff --git a/src/components/__tests__/Callbacks.test.tsx b/src/components/__tests__/Callbacks.test.tsx index 0f8cb390..added1da 100644 --- a/src/components/__tests__/Callbacks.test.tsx +++ b/src/components/__tests__/Callbacks.test.tsx @@ -31,7 +31,7 @@ describe('Components', () => { it('should correctly render CallbackTitle', () => { const callbackTitleViewElement = shallow( - , + , ).getElement(); expect(callbackTitleViewElement.props).toBeDefined(); expect(callbackTitleViewElement.props.className).toEqual('.test'); diff --git a/src/services/models/Callback.ts b/src/services/models/Callback.ts index 1a60ed3a..bbb04d37 100644 --- a/src/services/models/Callback.ts +++ b/src/services/models/Callback.ts @@ -39,7 +39,6 @@ export class CallbackModel { undefined, options, true, - this.name, ); this.operations.push(operation); diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index b5cf136c..9b468d70 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -86,7 +86,6 @@ export class OperationModel implements IMenuItem { parent: GroupModel | undefined, private options: RedocNormalizedOptions, isCallback: boolean = false, - callbackEventName?: string, ) { this.pointer = JsonPointer.compile(['paths', operationSpec.pathName, operationSpec.httpVerb]); @@ -112,22 +111,21 @@ export class OperationModel implements IMenuItem { JsonPointer.compile(['paths', operationSpec.pathName]), ); + this.name = getOperationSummary(operationSpec); + if (this.isCallback) { - // NOTE: Use callback's event name as the view label, not the operationID. - this.name = callbackEventName || getOperationSummary(operationSpec); // NOTE: Callbacks by default should not inherit the specification's global `security` definition. // Can be defined individually per-callback in the specification. Defaults to none. this.security = (operationSpec.security || []).map( security => new SecurityRequirementModel(security, parser), ); - // TODO: update getting pathInfo + // TODO: update getting pathInfo for overriding servers on path level this.servers = normalizeServers( '', operationSpec.servers || (pathInfo && pathInfo.servers) || [], ); } else { - this.name = getOperationSummary(operationSpec); this.security = (operationSpec.security || parser.spec.security || []).map( security => new SecurityRequirementModel(security, parser), ); @@ -256,6 +254,7 @@ export class OperationModel implements IMenuItem { @memoize get callbacks() { + console.log('this.operationSpec.callbacks', this.operationSpec.callbacks); return Object.keys(this.operationSpec.callbacks || []).map(callbackEventName => { return new CallbackModel( this.parser,