mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-22 00:26:34 +03:00
feat: add callbacks support (#1224)
Co-authored-by: Jonathan Bailey <jonathan_bailey@bose.com> Co-authored-by: Roman Hotsiy <gotsijroman@gmail.com>
This commit is contained in:
parent
5bd2e6227b
commit
57e93ec435
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
@ -50,11 +50,13 @@ $ npm run unit
|
|||
|
||||
# run e2e tests
|
||||
$ npm run e2e
|
||||
# Make sure you have created bundle before running e2e test
|
||||
# E.g. run `npm run bundle` and wait for the finishing process.
|
||||
|
||||
# open cypress UI to debug e2e test
|
||||
$ npm run cy:open
|
||||
|
||||
# run the full test suite, include linting / unit / e2e
|
||||
# run the unit tests (includes linting and license checks)
|
||||
$ npm test
|
||||
|
||||
# prepare bundles
|
||||
|
|
|
@ -489,6 +489,234 @@ paths:
|
|||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
/store/subscribe:
|
||||
post:
|
||||
tags:
|
||||
- store
|
||||
summary: Subscribe to the Store events
|
||||
description: Add subscription for a store events
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
callbackUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: This URL will be called by the server when the desired event will occur
|
||||
example: https://myserver.com/send/callback/here
|
||||
eventName:
|
||||
type: string
|
||||
description: Event name for the subscription
|
||||
enum:
|
||||
- orderInProgress
|
||||
- orderShipped
|
||||
- orderDelivered
|
||||
example: orderInProgress
|
||||
required:
|
||||
- callbackUrl
|
||||
- eventName
|
||||
responses:
|
||||
'201':
|
||||
description: Subscription added
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
subscriptionId:
|
||||
type: string
|
||||
example: AAA-123-BBB-456
|
||||
callbacks:
|
||||
orderInProgress:
|
||||
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
||||
servers:
|
||||
- url: //callback-url.path-level/v1
|
||||
description: Path level server 1
|
||||
- url: //callback-url.path-level/v2
|
||||
description: Path level server 2
|
||||
post:
|
||||
summary: Order in Progress (Summary)
|
||||
description: A callback triggered every time an Order is updated status to "inProgress" (Description)
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'https://more-details.com/demo'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
status:
|
||||
type: string
|
||||
example: 'inProgress'
|
||||
application/xml:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
example: |
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<orderId>123</orderId>
|
||||
<status>inProgress</status>
|
||||
<timestamp>2018-10-19T16:46:45Z</timestamp>
|
||||
</root>
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
someProp:
|
||||
type: string
|
||||
example: '123'
|
||||
'299':
|
||||
description: Response for cancelling subscription
|
||||
'500':
|
||||
description: Callback processing failed and retries will be performed
|
||||
x-code-samples:
|
||||
- lang: 'C#'
|
||||
source: |
|
||||
PetStore.v1.Pet pet = new PetStore.v1.Pet();
|
||||
pet.setApiKey("your api key");
|
||||
pet.petType = PetStore.v1.Pet.TYPE_DOG;
|
||||
pet.name = "Rex";
|
||||
// set other fields
|
||||
PetStoreResponse response = pet.create();
|
||||
if (response.statusCode == HttpStatusCode.Created)
|
||||
{
|
||||
// Successfully created
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something wrong -- check response for errors
|
||||
Console.WriteLine(response.getRawResponse());
|
||||
}
|
||||
- lang: PHP
|
||||
source: |
|
||||
$form = new \PetStore\Entities\Pet();
|
||||
$form->setPetType("Dog");
|
||||
$form->setName("Rex");
|
||||
// set other fields
|
||||
try {
|
||||
$pet = $client->pets()->create($form);
|
||||
} catch (UnprocessableEntityException $e) {
|
||||
var_dump($e->getErrors());
|
||||
}
|
||||
put:
|
||||
description: Order in Progress (Only Description)
|
||||
servers:
|
||||
- url: //callback-url.operation-level/v1
|
||||
description: Operation level server 1 (Operation override)
|
||||
- url: //callback-url.operation-level/v2
|
||||
description: Operation level server 2 (Operation override)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
status:
|
||||
type: string
|
||||
example: 'inProgress'
|
||||
application/xml:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
example: |
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<orderId>123</orderId>
|
||||
<status>inProgress</status>
|
||||
<timestamp>2018-10-19T16:46:45Z</timestamp>
|
||||
</root>
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
someProp:
|
||||
type: string
|
||||
example: '123'
|
||||
orderShipped:
|
||||
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
||||
post:
|
||||
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:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
estimatedDeliveryDate:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-11-11T16:00:00Z'
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
orderDelivered:
|
||||
'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:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
|
@ -955,7 +1183,7 @@ components:
|
|||
examples:
|
||||
Order:
|
||||
value:
|
||||
quantity: 1,
|
||||
shipDate: 2018-10-19T16:46:45Z,
|
||||
status: placed,
|
||||
quantity: 1
|
||||
shipDate: '2018-10-19T16:46:45Z'
|
||||
status: placed
|
||||
complete: false
|
||||
|
|
|
@ -6,7 +6,7 @@ describe('Menu', () => {
|
|||
it('should have valid items count', () => {
|
||||
cy.get('.menu-content')
|
||||
.find('li')
|
||||
.should('have.length', 6 + (2 + 8 + 1 + 4 + 2) + (1 + 8));
|
||||
.should('have.length', 6 + (2 + 8 + 1 + 4 + 2) + (1 + 8) + 1);
|
||||
});
|
||||
|
||||
it('should sync active menu items while scroll', () => {
|
||||
|
|
243
package-lock.json
generated
243
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.0.0-rc.24",
|
||||
"version": "2.0.0-rc.26",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2867,14 +2867,26 @@
|
|||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.1.tgz",
|
||||
"integrity": "sha512-FL/TdvchukRCuWVxT0YMO/7+L5TNeNrVFvRU2IY63aUyv9mpt8splf2NEr6qXtPo5fya5a66YohQKvGNmLrWNA==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
|
||||
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
|
@ -7270,22 +7282,182 @@
|
|||
"dev": true
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz",
|
||||
"integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==",
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.4.tgz",
|
||||
"integrity": "sha512-Is8+SzHv8K9STNadlBVpVhxXrSXxVgTyIvhdg2Qjak1SfSZ7iEozLHdwiX1jJ9lLFkcFJxqGK5s/cI7ZX+qGkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"neo-async": "^2.6.0",
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.6.1",
|
||||
"uglify-js": "^3.1.4"
|
||||
"uglify-js": "^3.1.4",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
|
||||
"integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.1"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz",
|
||||
"integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9444,8 +9616,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"resolved": "",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
|
@ -11485,6 +11656,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
|
||||
"integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==",
|
||||
"dev": true
|
||||
},
|
||||
"mobx": {
|
||||
"version": "4.15.4",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.15.4.tgz",
|
||||
|
@ -12098,24 +12275,6 @@
|
|||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "~0.0.1",
|
||||
"wordwrap": "~0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||
|
@ -14939,13 +15098,13 @@
|
|||
"dev": true
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
|
||||
"integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
|
||||
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
|
@ -15412,9 +15571,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz",
|
||||
"integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==",
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz",
|
||||
"integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -16308,8 +16467,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"resolved": "",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
|
@ -17407,8 +17565,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"resolved": "",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
|
@ -17776,12 +17933,6 @@
|
|||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
},
|
||||
"worker-farm": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
|
||||
|
|
35
src/components/CallbackSamples/CallbackReqSamples.tsx
Normal file
35
src/components/CallbackSamples/CallbackReqSamples.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import styled from '../../styled-components';
|
||||
import { DropdownProps } from '../../common-elements';
|
||||
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||
import { OperationModel } from '../../services/models';
|
||||
import { XPayloadSample } from '../../services/models/Operation';
|
||||
import { isPayloadSample } from '../../services';
|
||||
|
||||
export interface PayloadSampleProps {
|
||||
callback: OperationModel;
|
||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export class CallbackPayloadSample extends React.Component<PayloadSampleProps> {
|
||||
render() {
|
||||
const payloadSample = this.props.callback.codeSamples.find(sample =>
|
||||
isPayloadSample(sample),
|
||||
) as XPayloadSample | undefined;
|
||||
|
||||
if (!payloadSample) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PayloadSampleWrapper>
|
||||
<PayloadSamples content={payloadSample.requestBodyContent} />
|
||||
</PayloadSampleWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const PayloadSampleWrapper = styled.div`
|
||||
margin-top: 15px;
|
||||
`;
|
79
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
79
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import styled from '../../styled-components';
|
||||
import { RightPanelHeader } from '../../common-elements';
|
||||
import { RedocNormalizedOptions } from '../../services';
|
||||
import { CallbackModel } from '../../services/models';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { GenericChildrenSwitcher } from '../GenericChildrenSwitcher/GenericChildrenSwitcher';
|
||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||
import { InvertedSimpleDropdown, MimeLabel } from '../PayloadSamples/styled.elements';
|
||||
import { CallbackPayloadSample } from './CallbackReqSamples';
|
||||
|
||||
export interface CallbackSamplesProps {
|
||||
callbacks: CallbackModel[];
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CallbackSamples extends React.Component<CallbackSamplesProps> {
|
||||
static contextType = OptionsContext;
|
||||
context: RedocNormalizedOptions;
|
||||
|
||||
private renderDropdown = props => {
|
||||
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { callbacks } = this.props;
|
||||
|
||||
if (!callbacks || callbacks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const operations = callbacks
|
||||
.map(callback => callback.operations.map(operation => operation))
|
||||
.reduce((a, b) => a.concat(b), []);
|
||||
|
||||
const hasSamples = operations.some(operation => operation.codeSamples.length > 0);
|
||||
|
||||
if (!hasSamples) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dropdownOptions = operations.map((callback, idx) => {
|
||||
return {
|
||||
label: `${callback.httpVerb.toUpperCase()}: ${callback.name}`,
|
||||
value: idx.toString(),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RightPanelHeader> Callback payload samples </RightPanelHeader>
|
||||
|
||||
<SamplesWrapper>
|
||||
<GenericChildrenSwitcher
|
||||
items={operations}
|
||||
renderDropdown={this.renderDropdown}
|
||||
label={'Callback'}
|
||||
options={dropdownOptions}
|
||||
>
|
||||
{callback => (
|
||||
<CallbackPayloadSample
|
||||
key="callbackPayloadSample"
|
||||
callback={callback}
|
||||
renderDropdown={this.renderDropdown}
|
||||
/>
|
||||
)}
|
||||
</GenericChildrenSwitcher>
|
||||
</SamplesWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const SamplesWrapper = styled.div`
|
||||
background: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
`;
|
46
src/components/Callbacks/CallbackDetails.tsx
Normal file
46
src/components/Callbacks/CallbackDetails.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OperationModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { Endpoint } from '../Endpoint/Endpoint';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { Parameters } from '../Parameters/Parameters';
|
||||
import { ResponsesList } from '../Responses/ResponsesList';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
import { CallbackDetailsWrap } from './styled.elements';
|
||||
|
||||
export interface CallbackDetailsProps {
|
||||
operation: OperationModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CallbackDetails extends React.Component<CallbackDetailsProps> {
|
||||
render() {
|
||||
const { operation } = this.props;
|
||||
const { description, externalDocs } = operation;
|
||||
const hasDescription = !!(description || externalDocs);
|
||||
|
||||
return (
|
||||
<CallbackDetailsWrap>
|
||||
{hasDescription && (
|
||||
<Description>
|
||||
{description !== undefined && <Markdown source={description} />}
|
||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||
</Description>
|
||||
)}
|
||||
<Endpoint operation={this.props.operation} inverted={true} compact={true} />
|
||||
<Extensions extensions={operation.extensions} />
|
||||
<SecurityRequirements securities={operation.security} />
|
||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||
<ResponsesList responses={operation.responses} isCallback={operation.isCallback} />
|
||||
</CallbackDetailsWrap>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Description = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing.unit * 3}px;
|
||||
`;
|
30
src/components/Callbacks/CallbackOperation.tsx
Normal file
30
src/components/Callbacks/CallbackOperation.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OperationModel } from '../../services/models';
|
||||
import { StyledCallbackTitle } from './styled.elements';
|
||||
import { CallbackDetails } from './CallbackDetails';
|
||||
|
||||
@observer
|
||||
export class CallbackOperation extends React.Component<{ callbackOperation: OperationModel }> {
|
||||
toggle = () => {
|
||||
this.props.callbackOperation.toggle();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, expanded, httpVerb, deprecated } = this.props.callbackOperation;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledCallbackTitle
|
||||
onClick={this.toggle}
|
||||
name={name}
|
||||
opened={expanded}
|
||||
httpVerb={httpVerb}
|
||||
deprecated={deprecated}
|
||||
/>
|
||||
{expanded && <CallbackDetails operation={this.props.callbackOperation} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
54
src/components/Callbacks/CallbackTitle.tsx
Normal file
54
src/components/Callbacks/CallbackTitle.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { darken } from 'polished';
|
||||
import { ShelfIcon } from '../../common-elements';
|
||||
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;
|
||||
opened?: boolean;
|
||||
httpVerb: string;
|
||||
deprecated?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
||||
render() {
|
||||
const { name, opened, className, onClick, httpVerb, deprecated } = this.props;
|
||||
|
||||
return (
|
||||
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
|
||||
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
|
||||
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
|
||||
<CallbackName deprecated={deprecated}>{name}</CallbackName>
|
||||
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
|
||||
</CallbackTitleWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CallbackTitleWrapper = styled.div`
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
${ShelfIcon} {
|
||||
polygon {
|
||||
fill: ${({ theme }) => darken(theme.colors.tonalOffset, theme.colors.gray[100])};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CallbackName = styled.span<{ deprecated?: boolean }>`
|
||||
text-decoration: ${props => (props.deprecated ? 'line-through' : 'none')};
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const OperationBadgeStyled = styled(OperationBadge)`
|
||||
margin: 0px 5px 0px 0px;
|
||||
`;
|
40
src/components/Callbacks/CallbacksList.tsx
Normal file
40
src/components/Callbacks/CallbacksList.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { CallbackModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { CallbackOperation } from './CallbackOperation';
|
||||
|
||||
export interface CallbacksListProps {
|
||||
callbacks: CallbackModel[];
|
||||
}
|
||||
|
||||
export class CallbacksList extends React.PureComponent<CallbacksListProps> {
|
||||
render() {
|
||||
const { callbacks } = this.props;
|
||||
|
||||
if (!callbacks || callbacks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CallbacksHeader> Callbacks </CallbacksHeader>
|
||||
{callbacks.map(callback => {
|
||||
return callback.operations.map((operation, index) => {
|
||||
return (
|
||||
<CallbackOperation key={`${callback.name}_${index}`} callbackOperation={operation} />
|
||||
);
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CallbacksHeader = styled.h3`
|
||||
font-size: 1.3em;
|
||||
padding: 0.2em 0;
|
||||
margin: 3em 0 1.1em;
|
||||
color: ${({ theme }) => theme.colors.text.primary};
|
||||
font-weight: normal;
|
||||
`;
|
3
src/components/Callbacks/index.ts
Normal file
3
src/components/Callbacks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './CallbackOperation';
|
||||
export * from './CallbackTitle';
|
||||
export * from './CallbacksList';
|
18
src/components/Callbacks/styled.elements.ts
Normal file
18
src/components/Callbacks/styled.elements.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import styled from '../../styled-components';
|
||||
import { CallbackTitle } from './CallbackTitle';
|
||||
|
||||
export const StyledCallbackTitle = styled(CallbackTitle)`
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.5em;
|
||||
background-color: ${({ theme }) => theme.colors.gray[100]};
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CallbackDetailsWrap = styled.div`
|
||||
padding: 10px 25px;
|
||||
background-color: ${({ theme }) => theme.colors.gray[50]};
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
`;
|
|
@ -3,7 +3,6 @@ import * as React from 'react';
|
|||
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||
|
||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
||||
import { GroupModel, OperationModel } from '../../services/models';
|
||||
|
@ -18,7 +17,9 @@ export class ContentItems extends React.Component<{
|
|||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return items.map(item => <ContentItem item={item} key={item.id} />);
|
||||
return items.map(item => {
|
||||
return <ContentItem key={item.id} item={item} />;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<EndpointProps, EndpointState> {
|
|||
{options => (
|
||||
<OperationEndpointWrap>
|
||||
<EndpointInfo onClick={this.toggle} expanded={expanded} inverted={inverted}>
|
||||
<HttpVerb type={operation.httpVerb}> {operation.httpVerb}</HttpVerb>{' '}
|
||||
<HttpVerb type={operation.httpVerb} compact={this.props.compact}>
|
||||
{operation.httpVerb}
|
||||
</HttpVerb>
|
||||
<ServerRelativeURL>{operation.path}</ServerRelativeURL>
|
||||
<ShelfIcon
|
||||
float={'right'}
|
||||
|
|
|
@ -34,14 +34,14 @@ export const EndpointInfo = styled.div<{ expanded?: boolean; inverted?: boolean
|
|||
}
|
||||
`;
|
||||
|
||||
export const HttpVerb = styled.span.attrs((props: { type: string }) => ({
|
||||
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);')}
|
||||
`;
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { DropdownProps, DropdownOption } from '../../common-elements/dropdown';
|
||||
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||
|
||||
export interface GenericChildrenSwitcherProps<T> {
|
||||
items?: T[];
|
||||
options: DropdownOption[];
|
||||
label?: string;
|
||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||
children: (activeItem: T) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface GenericChildrenSwitcherState {
|
||||
activeItemIdx: number;
|
||||
}
|
||||
/**
|
||||
* TODO: Refactor this component:
|
||||
* Implement rendering dropdown/label directly in this component
|
||||
* Accept as a parameter mapper-function for building dropdown option labels
|
||||
*/
|
||||
@observer
|
||||
export class GenericChildrenSwitcher<T> extends React.Component<
|
||||
GenericChildrenSwitcherProps<T>,
|
||||
GenericChildrenSwitcherState
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeItemIdx: 0,
|
||||
};
|
||||
}
|
||||
|
||||
switchItem = ({ value }) => {
|
||||
if (this.props.items) {
|
||||
this.setState({
|
||||
activeItemIdx: parseInt(value, 10),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { items } = this.props;
|
||||
|
||||
if (!items || !items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Wrapper = ({ children }) =>
|
||||
this.props.label ? (
|
||||
<DropdownWrapper>
|
||||
<DropdownLabel>{this.props.label}</DropdownLabel>
|
||||
{children}
|
||||
</DropdownWrapper>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
{this.props.renderDropdown({
|
||||
value: this.props.options[this.state.activeItemIdx],
|
||||
options: this.props.options,
|
||||
onChange: this.switchItem,
|
||||
})}
|
||||
</Wrapper>
|
||||
|
||||
{this.props.children(items[this.state.activeItemIdx])}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,29 +1,26 @@
|
|||
import * as React from 'react';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements';
|
||||
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
import { ShareLink } from '../../common-elements/linkify';
|
||||
import { OperationModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { CallbacksList } from '../Callbacks';
|
||||
import { CallbackSamples } from '../CallbackSamples/CallbackSamples';
|
||||
import { Endpoint } from '../Endpoint/Endpoint';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { Parameters } from '../Parameters/Parameters';
|
||||
import { RequestSamples } from '../RequestSamples/RequestSamples';
|
||||
import { ResponsesList } from '../Responses/ResponsesList';
|
||||
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
||||
|
||||
import { OperationModel as OperationType } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
|
||||
const OperationRow = styled(Row)`
|
||||
backface-visibility: hidden;
|
||||
contain: content;
|
||||
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
@ -32,7 +29,7 @@ const Description = styled.div`
|
|||
`;
|
||||
|
||||
export interface OperationProps {
|
||||
operation: OperationType;
|
||||
operation: OperationModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
|
@ -63,11 +60,13 @@ export class Operation extends React.Component<OperationProps> {
|
|||
<SecurityRequirements securities={operation.security} />
|
||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||
<ResponsesList responses={operation.responses} />
|
||||
<CallbacksList callbacks={operation.callbacks} />
|
||||
</MiddlePanel>
|
||||
<DarkRightPanel>
|
||||
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
|
||||
<RequestSamples operation={operation} />
|
||||
<ResponseSamples operation={operation} />
|
||||
<CallbackSamples callbacks={operation.callbacks} />
|
||||
</DarkRightPanel>
|
||||
</OperationRow>
|
||||
)}
|
||||
|
|
|
@ -4,14 +4,16 @@ import ReactDropdown from 'react-dropdown';
|
|||
|
||||
import { transparentize } from 'polished';
|
||||
import styled from '../../styled-components';
|
||||
|
||||
import { StyledDropdown } from '../../common-elements';
|
||||
|
||||
export const MimeLabel = styled.div`
|
||||
padding: 12px;
|
||||
padding: 0.9em;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
|
||||
font-size: 0.929em;
|
||||
line-height: 1.5em;
|
||||
`;
|
||||
|
||||
export const DropdownLabel = styled.span`
|
||||
|
@ -36,6 +38,11 @@ export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
|||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
.Dropdown-placeholder {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.Dropdown-control {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
@ -55,6 +62,11 @@ export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
|||
.Dropdown-menu {
|
||||
margin: 0;
|
||||
margin-top: 2px;
|
||||
.Dropdown-option {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -4,20 +4,21 @@ import styled from '../../styled-components';
|
|||
import { ResponseView } from './Response';
|
||||
|
||||
const ResponsesHeader = styled.h3`
|
||||
font-size: 18px;
|
||||
font-size: 1.3em;
|
||||
padding: 0.2em 0;
|
||||
margin: 3em 0 1.1em;
|
||||
color: #253137;
|
||||
color: ${({ theme }) => theme.colors.text.primary};
|
||||
font-weight: normal;
|
||||
`;
|
||||
|
||||
export interface ResponseListProps {
|
||||
responses: ResponseModel[];
|
||||
isCallback?: boolean;
|
||||
}
|
||||
|
||||
export class ResponsesList extends React.PureComponent<ResponseListProps> {
|
||||
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<ResponseListProps> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ResponsesHeader> Responses </ResponsesHeader>
|
||||
<ResponsesHeader>{isCallback ? 'Callback responses' : 'Responses'}</ResponsesHeader>
|
||||
{responses.map(response => {
|
||||
return <ResponseView key={response.code} response={response} />;
|
||||
})}
|
||||
|
|
59
src/components/__tests__/Callbacks.test.tsx
Normal file
59
src/components/__tests__/Callbacks.test.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* tslint:disable:no-implicit-dependencies */
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OpenAPIParser } from '../../services';
|
||||
import { CallbackModel } from '../../services/models/Callback';
|
||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||
import { CallbacksList, CallbackTitle, CallbackOperation } from '../Callbacks';
|
||||
import * as simpleCallbackFixture from './fixtures/simple-callback.json';
|
||||
|
||||
const options = new RedocNormalizedOptions({});
|
||||
describe('Components', () => {
|
||||
describe('Callbacks', () => {
|
||||
it('should correctly render CallbackView', () => {
|
||||
const parser = new OpenAPIParser(simpleCallbackFixture, undefined, options);
|
||||
const callback = new CallbackModel(
|
||||
parser,
|
||||
'Test.Callback',
|
||||
{ $ref: '#/components/callbacks/Test' },
|
||||
'',
|
||||
options,
|
||||
);
|
||||
// There should be 1 operation defined in simple-callback.json, just get it manually for readability.
|
||||
const callbackViewElement = shallow(
|
||||
<CallbackOperation key={callback.name} callbackOperation={callback.operations[0]} />,
|
||||
).getElement();
|
||||
expect(callbackViewElement.props).toBeDefined();
|
||||
expect(callbackViewElement.props.children).toBeDefined();
|
||||
expect(callbackViewElement.props.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should correctly render CallbackTitle', () => {
|
||||
const callbackTitleViewElement = shallow(
|
||||
<CallbackTitle name={'Test'} className={'.test'} onClick={undefined} httpVerb={'get'} />,
|
||||
).getElement();
|
||||
expect(callbackTitleViewElement.props).toBeDefined();
|
||||
expect(callbackTitleViewElement.props.className).toEqual('.test');
|
||||
expect(callbackTitleViewElement.props.onClick).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should correctly render CallbacksList', () => {
|
||||
const parser = new OpenAPIParser(simpleCallbackFixture, undefined, options);
|
||||
const callback = new CallbackModel(
|
||||
parser,
|
||||
'Test.Callback',
|
||||
{ $ref: '#/components/callbacks/Test' },
|
||||
'',
|
||||
options,
|
||||
);
|
||||
const callbacksListViewElement = shallow(
|
||||
<CallbacksList callbacks={[callback]} />,
|
||||
).getElement();
|
||||
expect(callbacksListViewElement.props).toBeDefined();
|
||||
expect(callbacksListViewElement.props.children).toBeDefined();
|
||||
expect(callbacksListViewElement.props.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
66
src/components/__tests__/fixtures/simple-callback.json
Normal file
66
src/components/__tests__/fixtures/simple-callback.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "Foo"
|
||||
},
|
||||
"components": {
|
||||
"callbacks": {
|
||||
"Test": {
|
||||
"/test": {
|
||||
"post": {
|
||||
"operationId": "testCallback",
|
||||
"description": "Test callback.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "TestTitle",
|
||||
"type": "object",
|
||||
"description": "Test description",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of response.",
|
||||
"enum": [
|
||||
"TestResponse.Complete"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FAILURE",
|
||||
"SUCCESS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Test-Header",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"example": "1",
|
||||
"description": "This is a test header parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Test response."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,16 @@
|
|||
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
|
||||
import {
|
||||
OpenAPIOperation,
|
||||
OpenAPIParameter,
|
||||
OpenAPISpec,
|
||||
OpenAPITag,
|
||||
Referenced,
|
||||
OpenAPIServer,
|
||||
} from '../types';
|
||||
import {
|
||||
isOperationName,
|
||||
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||
setSecuritySchemePrefix,
|
||||
JsonPointer,
|
||||
} from '../utils';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { GroupModel, OperationModel } from './models';
|
||||
|
@ -15,9 +23,11 @@ export type TagInfo = OpenAPITag & {
|
|||
};
|
||||
|
||||
export type ExtendedOpenAPIOperation = {
|
||||
pointer: string;
|
||||
pathName: string;
|
||||
httpVerb: string;
|
||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||
pathServers: Array<OpenAPIServer> | undefined;
|
||||
} & OpenAPIOperation;
|
||||
|
||||
export type TagsInfoMap = Dict<TagInfo>;
|
||||
|
@ -237,8 +247,10 @@ export class MenuBuilder {
|
|||
tag.operations.push({
|
||||
...operationInfo,
|
||||
pathName,
|
||||
pointer: JsonPointer.compile(['paths', pathName, operationName]),
|
||||
httpVerb: operationName,
|
||||
pathParameters: path.parameters || [],
|
||||
pathServers: path.servers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
64
src/services/__tests__/fixtures/callback.json
Normal file
64
src/services/__tests__/fixtures/callback.json
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "Foo"
|
||||
},
|
||||
"components": {
|
||||
"callbacks": {
|
||||
"Test": {
|
||||
"post": {
|
||||
"operationId": "testCallback",
|
||||
"description": "Test callback.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "TestTitle",
|
||||
"type": "object",
|
||||
"description": "Test description",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of response.",
|
||||
"enum": [
|
||||
"TestResponse.Complete"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FAILURE",
|
||||
"SUCCESS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Test-Header",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"example": "1",
|
||||
"description": "This is a test header parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Test response."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/services/__tests__/models/Callback.test.ts
Normal file
26
src/services/__tests__/models/Callback.test.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { CallbackModel } from '../../models/Callback';
|
||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||
|
||||
const opts = new RedocNormalizedOptions({});
|
||||
|
||||
describe('Models', () => {
|
||||
describe('CallbackModel', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const spec = require('../fixtures/callback.json');
|
||||
const parser = new OpenAPIParser(spec, undefined, opts);
|
||||
|
||||
test('basic callback details', () => {
|
||||
const callback = new CallbackModel(
|
||||
parser,
|
||||
'Test.Callback',
|
||||
{ $ref: '#/components/callbacks/Test' },
|
||||
'',
|
||||
opts,
|
||||
);
|
||||
expect(callback.name).toEqual('Test.Callback');
|
||||
expect(callback.operations.length).toEqual(0);
|
||||
expect(callback.expanded).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
56
src/services/models/Callback.ts
Normal file
56
src/services/models/Callback.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { action, observable } from 'mobx';
|
||||
|
||||
import { OpenAPICallback, Referenced } from '../../types';
|
||||
import { isOperationName, JsonPointer } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { OperationModel } from './Operation';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
export class CallbackModel {
|
||||
@observable
|
||||
expanded: boolean;
|
||||
name: string;
|
||||
operations: OperationModel[] = [];
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
name: string,
|
||||
infoOrRef: Referenced<OpenAPICallback>,
|
||||
pointer: string,
|
||||
options: RedocNormalizedOptions,
|
||||
) {
|
||||
this.name = name;
|
||||
const paths = parser.deref<OpenAPICallback>(infoOrRef);
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
for (const pathName of Object.keys(paths)) {
|
||||
const path = paths[pathName];
|
||||
const operations = Object.keys(path).filter(isOperationName);
|
||||
for (const operationName of operations) {
|
||||
const operationInfo = path[operationName];
|
||||
|
||||
const operation = new OperationModel(
|
||||
parser,
|
||||
{
|
||||
...operationInfo,
|
||||
pathName,
|
||||
pointer: JsonPointer.compile([pointer, name, pathName, operationName]),
|
||||
httpVerb: operationName,
|
||||
pathParameters: path.parameters || [],
|
||||
pathServers: path.servers,
|
||||
},
|
||||
undefined,
|
||||
options,
|
||||
true,
|
||||
);
|
||||
|
||||
this.operations.push(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
toggle() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
}
|
|
@ -4,19 +4,13 @@ import { IMenuItem } from '../MenuStore';
|
|||
import { GroupModel } from './Group.model';
|
||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||
|
||||
import {
|
||||
OpenAPIExternalDocumentation,
|
||||
OpenAPIPath,
|
||||
OpenAPIServer,
|
||||
OpenAPIXCodeSample,
|
||||
} from '../../types';
|
||||
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||
|
||||
import {
|
||||
extractExtensions,
|
||||
getOperationSummary,
|
||||
getStatusCodeType,
|
||||
isStatusCode,
|
||||
JsonPointer,
|
||||
memoize,
|
||||
mergeParams,
|
||||
normalizeServers,
|
||||
|
@ -26,12 +20,13 @@ import {
|
|||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { CallbackModel } from './Callback';
|
||||
import { FieldModel } from './Field';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
import { RequestBodyModel } from './RequestBody';
|
||||
import { ResponseModel } from './Response';
|
||||
|
||||
interface XPayloadSample {
|
||||
export interface XPayloadSample {
|
||||
lang: 'payload';
|
||||
label: string;
|
||||
requestBodyContent: MediaContentModel;
|
||||
|
@ -77,23 +72,17 @@ export class OperationModel implements IMenuItem {
|
|||
servers: OpenAPIServer[];
|
||||
security: SecurityRequirementModel[];
|
||||
extensions: Dict<any>;
|
||||
isCallback: boolean;
|
||||
|
||||
constructor(
|
||||
private parser: OpenAPIParser,
|
||||
private operationSpec: ExtendedOpenAPIOperation,
|
||||
parent: GroupModel | undefined,
|
||||
private options: RedocNormalizedOptions,
|
||||
isCallback: boolean = false,
|
||||
) {
|
||||
this.pointer = JsonPointer.compile(['paths', operationSpec.pathName, operationSpec.httpVerb]);
|
||||
this.pointer = operationSpec.pointer;
|
||||
|
||||
this.id =
|
||||
operationSpec.operationId !== undefined
|
||||
? 'operation/' + operationSpec.operationId
|
||||
: parent !== undefined
|
||||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
this.description = operationSpec.description;
|
||||
this.parent = parent;
|
||||
this.externalDocs = operationSpec.externalDocs;
|
||||
|
@ -103,19 +92,36 @@ export class OperationModel implements IMenuItem {
|
|||
this.deprecated = !!operationSpec.deprecated;
|
||||
this.operationId = operationSpec.operationId;
|
||||
this.path = operationSpec.pathName;
|
||||
this.isCallback = isCallback;
|
||||
|
||||
const pathInfo = parser.byRef<OpenAPIPath>(
|
||||
JsonPointer.compile(['paths', operationSpec.pathName]),
|
||||
);
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
|
||||
this.servers = normalizeServers(
|
||||
parser.specUrl,
|
||||
operationSpec.servers || (pathInfo && pathInfo.servers) || parser.spec.servers || [],
|
||||
);
|
||||
if (this.isCallback) {
|
||||
// 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),
|
||||
);
|
||||
|
||||
this.security = (operationSpec.security || parser.spec.security || []).map(
|
||||
security => new SecurityRequirementModel(security, parser),
|
||||
);
|
||||
// TODO: update getting pathInfo for overriding servers on path level
|
||||
this.servers = normalizeServers('', operationSpec.servers || operationSpec.pathServers || []);
|
||||
} else {
|
||||
this.id =
|
||||
operationSpec.operationId !== undefined
|
||||
? 'operation/' + operationSpec.operationId
|
||||
: parent !== undefined
|
||||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
|
||||
this.security = (operationSpec.security || parser.spec.security || []).map(
|
||||
security => new SecurityRequirementModel(security, parser),
|
||||
);
|
||||
|
||||
this.servers = normalizeServers(
|
||||
parser.specUrl,
|
||||
operationSpec.servers || operationSpec.pathServers || parser.spec.servers || [],
|
||||
);
|
||||
}
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(operationSpec, options.showExtensions);
|
||||
|
@ -138,6 +144,14 @@ export class OperationModel implements IMenuItem {
|
|||
this.active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle expansion in middle panel (for callbacks, which are operations)
|
||||
*/
|
||||
@action
|
||||
toggle() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
expand() {
|
||||
if (this.parent) {
|
||||
this.parent.expand();
|
||||
|
@ -224,4 +238,17 @@ export class OperationModel implements IMenuItem {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
@memoize
|
||||
get callbacks() {
|
||||
return Object.keys(this.operationSpec.callbacks || []).map(callbackEventName => {
|
||||
return new CallbackModel(
|
||||
this.parser,
|
||||
callbackEventName,
|
||||
this.operationSpec.callbacks![callbackEventName],
|
||||
this.pointer,
|
||||
this.options,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,3 +10,4 @@ export * from './Schema';
|
|||
export * from './Field';
|
||||
export * from './ApiInfo';
|
||||
export * from './SecuritySchemes';
|
||||
export * from './Callback';
|
||||
|
|
|
@ -10,9 +10,7 @@ const {
|
|||
createGlobalStyle,
|
||||
keyframes,
|
||||
ThemeProvider,
|
||||
} = (styledComponents as any) as styledComponents.ThemedStyledComponentsModule<
|
||||
ResolvedThemeInterface
|
||||
>;
|
||||
} = styledComponents as styledComponents.ThemedStyledComponentsModule<ResolvedThemeInterface>;
|
||||
|
||||
export const media = {
|
||||
lessThan(breakpoint, print?: boolean) {
|
||||
|
|
|
@ -37,6 +37,10 @@ const defaultTheme: ThemeInterface = {
|
|||
dark: ({ colors }) => darken(colors.tonalOffset, colors.error.main),
|
||||
contrastText: ({ colors }) => readableColor(colors.error.main),
|
||||
},
|
||||
gray: {
|
||||
50: '#FAFAFA',
|
||||
100: '#F5F5F5',
|
||||
},
|
||||
text: {
|
||||
primary: '#333333',
|
||||
secondary: ({ colors }) => lighten(colors.tonalOffset, colors.text.primary),
|
||||
|
@ -229,6 +233,10 @@ export interface ResolvedThemeInterface {
|
|||
success: ColorSetting;
|
||||
warning: ColorSetting;
|
||||
error: ColorSetting;
|
||||
gray: {
|
||||
50: string;
|
||||
100: string;
|
||||
};
|
||||
border: {
|
||||
light: string;
|
||||
dark: string;
|
||||
|
|
2
src/types/open-api.d.ts
vendored
2
src/types/open-api.d.ts
vendored
|
@ -196,7 +196,7 @@ export interface OpenAPILink {
|
|||
export type OpenAPIHeader = Omit<OpenAPIParameter, 'in' | 'name'>;
|
||||
|
||||
export interface OpenAPICallback {
|
||||
$ref?: string;
|
||||
[name: string]: OpenAPIPath;
|
||||
}
|
||||
|
||||
export interface OpenAPIComponents {
|
||||
|
|
|
@ -8,9 +8,9 @@ const getInnerHtml = require('./helpers').getInnerHtml;
|
|||
const URL = 'index.html';
|
||||
|
||||
function waitForInit() {
|
||||
var EC = protractor.ExpectedConditions;
|
||||
var $apiInfo = $('api-info');
|
||||
var $errorMessage = $('.redoc-error')
|
||||
const EC = protractor.ExpectedConditions;
|
||||
const $apiInfo = $('api-info');
|
||||
const $errorMessage = $('.redoc-error');
|
||||
browser.wait(EC.or(EC.visibilityOf($apiInfo), EC.visibilityOf($errorMessage)), 60000);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ function basicTests(swaggerUrl, title) {
|
|||
specUrl += `?url=${encodeURIComponent(swaggerUrl)}`;
|
||||
}
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
|
@ -31,11 +31,11 @@ function basicTests(swaggerUrl, title) {
|
|||
verifyNoBrowserErrors();
|
||||
});
|
||||
|
||||
it('should init redoc without errors', (done) => {
|
||||
let $redoc = $('redoc');
|
||||
it('should init redoc without errors', done => {
|
||||
const $redoc = $('redoc');
|
||||
expect($redoc.isPresent()).toBe(true);
|
||||
setTimeout(() => {
|
||||
let $operations = $$('operation');
|
||||
const $operations = $$('operation');
|
||||
expect($operations.count()).toBeGreaterThan(0);
|
||||
done();
|
||||
});
|
||||
|
@ -45,11 +45,10 @@ function basicTests(swaggerUrl, title) {
|
|||
|
||||
basicTests(null, 'Extended Petstore');
|
||||
|
||||
|
||||
describe('Scroll sync', () => {
|
||||
let specUrl = URL;
|
||||
const specUrl = URL;
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
|
@ -57,25 +56,31 @@ describe('Scroll sync', () => {
|
|||
|
||||
it('should update active menu entries on page scroll forwards', () => {
|
||||
scrollToEl('[section="tag/store"]').then(() => {
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active > .menu-item-header')).toContain('store');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active > .menu-item-header')).toContain(
|
||||
'store',
|
||||
);
|
||||
expect(getInnerHtml('.selected-tag')).toContain('store');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update active menu entries on page scroll backwards', () => {
|
||||
scrollToEl('[operation-id="getPetById"]').then(() => {
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active .menu-item-header')).toContain('pet');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active .menu-item-header')).toContain(
|
||||
'pet',
|
||||
);
|
||||
expect(getInnerHtml('.selected-tag')).toContain('pet');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-2.active .menu-item-header')).toContain('Find pet by ID');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-2.active .menu-item-header')).toContain(
|
||||
'Find pet by ID',
|
||||
);
|
||||
expect(getInnerHtml('.selected-endpoint')).toContain('Find pet by ID');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language tabs sync', () => {
|
||||
let specUrl = URL;
|
||||
const specUrl = URL;
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
|
@ -84,10 +89,10 @@ describe('Language tabs sync', () => {
|
|||
// skip as it fails for no reason on IE on sauce-labs
|
||||
// TODO: fixme
|
||||
xit('should sync language tabs', () => {
|
||||
var $item = $$('[operation-id="addPet"] tabs > ul > li').last();
|
||||
const $item = $$('[operation-id="addPet"] tabs > ul > li').last();
|
||||
// check if correct item
|
||||
expect($item.getText()).toContain('PHP');
|
||||
var EC = protractor.ExpectedConditions;
|
||||
const EC = protractor.ExpectedConditions;
|
||||
browser.wait(EC.elementToBeClickable($item), 5000);
|
||||
$item.click().then(() => {
|
||||
expect($('[operation-id="updatePet"] li.active').getText()).toContain('PHP');
|
||||
|
@ -96,8 +101,7 @@ describe('Language tabs sync', () => {
|
|||
});
|
||||
|
||||
if (process.env.JOB === 'e2e-guru') {
|
||||
describe('APIs.guru specs test', ()=> {
|
||||
|
||||
describe('APIs.guru specs test', () => {
|
||||
// global.apisGuruList was loaded in onPrepare method of protractor config
|
||||
let apisGuruList = global.apisGuruList;
|
||||
|
||||
|
@ -118,11 +122,11 @@ if (process.env.JOB === 'e2e-guru') {
|
|||
console.log('Running on a short APIs guru list');
|
||||
apisGuruList = eachNth(apisGuruList, 20);
|
||||
} else {
|
||||
console.log('Running on full APIs guru list')
|
||||
console.log('Running on full APIs guru list');
|
||||
}
|
||||
|
||||
for (let apiName of Object.keys(apisGuruList)) {
|
||||
let apiInfo = apisGuruList[apiName].versions[apisGuruList[apiName].preferred];
|
||||
for (const apiName of Object.keys(apisGuruList)) {
|
||||
const apiInfo = apisGuruList[apiName].versions[apisGuruList[apiName].preferred];
|
||||
let url = apiInfo.swaggerUrl;
|
||||
|
||||
// temporary hack due to this issue: https://github.com/substack/https-browserify/issues/6
|
||||
|
|
Loading…
Reference in New Issue
Block a user