Add tempest request/responses to the examples
This allows some back-filling of missinge request/response examples with examples taken from a tempest run.
This commit is contained in:
parent
bbe662d728
commit
e79ef40108
|
@ -0,0 +1,55 @@
|
|||
Tempest Examples
|
||||
================
|
||||
|
||||
To back fill the missing examples from the WADL, tempest result logs
|
||||
can be used.
|
||||
|
||||
If you run tempest with a `logging.conf` like. The one below then the
|
||||
resulting log `requests.log` will be suitable for processing.
|
||||
|
||||
::
|
||||
|
||||
[loggers]
|
||||
keys=root,tempest_http
|
||||
|
||||
[handlers]
|
||||
keys=file,devel
|
||||
|
||||
[formatters]
|
||||
keys=simple,dumb
|
||||
|
||||
[logger_root]
|
||||
level=DEBUG
|
||||
handlers=devel
|
||||
|
||||
[logger_tempest_http]
|
||||
level=DEBUG
|
||||
handlers=file
|
||||
qualname=tempest_lib.common.rest_client
|
||||
|
||||
[handler_file]
|
||||
class=FileHandler
|
||||
level=DEBUG
|
||||
args=('requests.log', 'w+')
|
||||
formatter=dumb
|
||||
|
||||
[handler_devel]
|
||||
class=StreamHandler
|
||||
level=INFO
|
||||
args=(sys.stdout,)
|
||||
formatter=simple
|
||||
|
||||
[formatter_simple]
|
||||
format=%(asctime)s.%(msecs)03d %(process)d %(levelname)s: %(message)s
|
||||
|
||||
[formatter_dumb]
|
||||
format=%(message)s
|
||||
|
||||
|
||||
Once the tempest run has completed, you can then process the log into
|
||||
a format for the WADL conversion process. This is done using the `fairy-slipper-tempest-log` tool::
|
||||
|
||||
fairy-slipper-tempest-log -o conversion_files/ requests.log
|
||||
|
||||
Where `conversion_files/` is the directory you are using to store the
|
||||
intermediate files during the migration from docbook.
|
|
@ -42,10 +42,16 @@ TMPL_API = """
|
|||
{% if request['examples']['application/json'] %}
|
||||
:requestexample: {{version}}/examples/{{request['id']}}_req.json
|
||||
{%- endif -%}
|
||||
{% if request['examples']['text/plain'] %}
|
||||
:requestexample: {{version}}/examples/{{request['id']}}_req.txt
|
||||
{%- endif -%}
|
||||
{% for status_code, response in request.responses.items() -%}
|
||||
{%- if response['examples']['application/json'] %}
|
||||
:responseexample {{status_code}}: {{version}}/examples/{{request['id']}}_resp_{{status_code}}.json
|
||||
{%- endif -%}
|
||||
{%- if response['examples']['text/plain'] %}
|
||||
:responseexample {{status_code}}: {{version}}/examples/{{request['id']}}_resp_{{status_code}}.txt
|
||||
{%- endif -%}
|
||||
{% endfor -%}
|
||||
{% for mime in request.consumes %}
|
||||
:accepts: {{mime}}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
# Copyright (c) 2015 Russell Sim <russell.sim@gmail.com>
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import logging
|
||||
from os import path
|
||||
import re
|
||||
import urlparse
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PORTS = {
|
||||
'5000': 'identity',
|
||||
'35357': 'identity-admin',
|
||||
'8774': 'compute',
|
||||
'8776': 'volume',
|
||||
'8773': 'compute-ec2',
|
||||
'9292': 'image',
|
||||
}
|
||||
REQUEST_RE = re.compile("Request (?P<test>\([^()]+\)):"
|
||||
" (?P<status_code>\d+)"
|
||||
" (?P<method>[A-Z]+) (?P<url>\S+)")
|
||||
|
||||
|
||||
def parse_logfile(log_file):
|
||||
"""Yet another shonky stream parser."""
|
||||
calls = []
|
||||
current_request = {}
|
||||
current_response = {}
|
||||
for line in log_file:
|
||||
request = REQUEST_RE.match(line)
|
||||
if request:
|
||||
if current_request and current_response:
|
||||
calls.append((current_request, current_response))
|
||||
request_dict = request.groupdict()
|
||||
url = urlparse.urlsplit(request_dict['url'])
|
||||
port = url.netloc.split(':')[-1]
|
||||
service = DEFAULT_PORTS[port]
|
||||
current_request = {
|
||||
'service': service,
|
||||
'url': urlparse.urlunsplit(('', '') + url[2:]),
|
||||
'method': request_dict['method']}
|
||||
current_response = {
|
||||
'status_code': request_dict['status_code']}
|
||||
else:
|
||||
try:
|
||||
key, value = line.split(':', 1)
|
||||
except ValueError:
|
||||
# For some wacky reason, when you request JSON,
|
||||
# sometimes you get text. Handle this rad behaviour.
|
||||
if current_response.get('headers') is not None:
|
||||
current_response['body'] += line
|
||||
else:
|
||||
current_request['body'] += line
|
||||
continue
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key == 'Request - Headers':
|
||||
current_request['headers'] = eval(value)
|
||||
if key == 'Response - Headers':
|
||||
current_response['headers'] = eval(value)
|
||||
if key == 'Body':
|
||||
if value == 'None':
|
||||
body = None
|
||||
elif value == '':
|
||||
body = None
|
||||
else:
|
||||
try:
|
||||
body = json.loads(value)
|
||||
body = json.dumps(body, indent=2)
|
||||
except ValueError:
|
||||
body = value
|
||||
log.info("Failed to parse %r", value)
|
||||
if current_response.get('headers') is not None:
|
||||
current_response['body'] = body
|
||||
else:
|
||||
current_request['body'] = body
|
||||
else:
|
||||
calls.append((current_request, current_response))
|
||||
return calls
|
||||
|
||||
|
||||
def main1(log_file, output_dir):
|
||||
log.info('Reading %s' % log_file)
|
||||
calls = parse_logfile(open(log_file))
|
||||
services = defaultdict(list)
|
||||
for call in calls:
|
||||
services[call[0]['service']].append(call)
|
||||
for service, calls in services.items():
|
||||
pathname = path.join(output_dir, '%s-examples.json' % (service))
|
||||
with open(pathname, 'w') as out_file:
|
||||
json.dump(calls, out_file, indent=2)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='count', default=0,
|
||||
help="Increase verbosity (specify multiple times for more)")
|
||||
parser.add_argument(
|
||||
'-o', '--output-dir', action='store',
|
||||
help="The directory to output the JSON files too.")
|
||||
parser.add_argument(
|
||||
'filename',
|
||||
help="File to convert")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
log_level = logging.WARNING
|
||||
if args.verbose == 1:
|
||||
log_level = logging.INFO
|
||||
elif args.verbose >= 2:
|
||||
log_level = logging.DEBUG
|
||||
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format='%(asctime)s %(name)s %(levelname)s %(message)s')
|
||||
|
||||
filename = path.abspath(args.filename)
|
||||
|
||||
main1(filename, output_dir=args.output_dir)
|
|
@ -29,6 +29,7 @@ import textwrap
|
|||
import xml.sax
|
||||
|
||||
import prettytable
|
||||
from jinja2 import Environment
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -105,6 +106,23 @@ MIME_MAP = {
|
|||
|
||||
VERSION_RE = re.compile('v[0-9\.]+')
|
||||
WHITESPACE_RE = re.compile('[\s]+', re.MULTILINE)
|
||||
URL_TEMPLATE_RE = re.compile('{[^{}]+}')
|
||||
|
||||
environment = Environment()
|
||||
HTTP_REQUEST = """{{ method }} {{ url }} HTTP/1.1
|
||||
{% for key, value in headers.items() -%}
|
||||
{{ key }}: {{ value }}
|
||||
{% endfor %}
|
||||
"""
|
||||
HTTP_REQUEST_TMPL = environment.from_string(HTTP_REQUEST)
|
||||
|
||||
HTTP_RESPONSE = """HTTP/1.1 {{ status_code }}
|
||||
{% for key, value in headers.items() -%}
|
||||
{{ key }}: {{ value }}
|
||||
{% endfor %}
|
||||
{{ body }}
|
||||
"""
|
||||
HTTP_RESPONSE_TMPL = environment.from_string(HTTP_RESPONSE)
|
||||
|
||||
|
||||
def create_parameter(name, _in, description='',
|
||||
|
@ -708,6 +726,15 @@ def main1(source_file, output_dir):
|
|||
for filepath in api_ref['file_tags'].keys():
|
||||
files.add(filepath.split('#', 1)[0])
|
||||
|
||||
# Load supplementary examples file
|
||||
examples_file = path.join(path.dirname(source_file),
|
||||
api_ref['service'] + '-examples.json')
|
||||
if path.exists(examples_file):
|
||||
log.info('Reading examples from %s' % examples_file)
|
||||
examples = json.load(open(examples_file))
|
||||
else:
|
||||
examples = []
|
||||
|
||||
output = {
|
||||
u'info': {
|
||||
'version': api_ref['version'],
|
||||
|
@ -736,6 +763,43 @@ def main1(source_file, output_dir):
|
|||
for urlpath, apis in ch.apis.items():
|
||||
output['paths'][urlpath].extend(apis)
|
||||
output['definitions'].update(ch.schemas)
|
||||
|
||||
for ex_request, ex_response in examples:
|
||||
for urlpath in output['paths']:
|
||||
url_matcher = "^" + URL_TEMPLATE_RE.sub('[^/]+', urlpath) + "$"
|
||||
if re.match(url_matcher, ex_request['url']):
|
||||
if len(output['paths'][urlpath]) > 1:
|
||||
# Skip any of the multi-payload endpoints. They
|
||||
# are madness.
|
||||
break
|
||||
|
||||
# Override any requests
|
||||
try:
|
||||
operation = output['paths'][urlpath][0]
|
||||
except:
|
||||
log.warning("Couldn't find any operations for %s", urlpath)
|
||||
break
|
||||
request = HTTP_REQUEST_TMPL.render(
|
||||
headers=ex_request['headers'],
|
||||
method=ex_request['method'],
|
||||
url=ex_request['url'])
|
||||
operation['examples'] = {'text/plain': request}
|
||||
|
||||
# Override any responses
|
||||
status_code = ex_response['status_code']
|
||||
response = HTTP_RESPONSE_TMPL.render(
|
||||
status_code=status_code,
|
||||
headers=ex_response['headers'],
|
||||
body=ex_response['body'] or '')
|
||||
if status_code in operation['responses']:
|
||||
operation['responses'][status_code]['examples'] = \
|
||||
{'text/plain': response}
|
||||
else:
|
||||
operation['responses'][status_code] = \
|
||||
{'examples': {'text/plain': response}}
|
||||
else:
|
||||
log.warning("Couldn't find matching URL for %s" % urlpath)
|
||||
|
||||
os.chdir(output_dir)
|
||||
pathname = '%s-%s-swagger.json' % (api_ref['service'],
|
||||
api_ref['version'])
|
||||
|
|
|
@ -41,9 +41,12 @@ class JSONFileController(object):
|
|||
self.filepath = filepath
|
||||
|
||||
@expose('json')
|
||||
@expose(content_type='text/plain')
|
||||
def _default(self):
|
||||
if path.exists(self.filepath + '.json'):
|
||||
self.filepath = self.filepath + '.json'
|
||||
if path.exists(self.filepath + '.txt'):
|
||||
self.filepath = self.filepath + '.txt'
|
||||
if not path.exists(self.filepath):
|
||||
response.status = 404
|
||||
return response
|
||||
|
@ -84,7 +87,7 @@ class DocController(object):
|
|||
'paths': json['paths'],
|
||||
'tags': json['tags']}
|
||||
|
||||
@expose('json')
|
||||
@expose()
|
||||
def _lookup(self, *components):
|
||||
if len(components) != 2 and len(components) != 3:
|
||||
return
|
||||
|
@ -92,9 +95,10 @@ class DocController(object):
|
|||
if components[0] == 'examples':
|
||||
example = components[1]
|
||||
filepath = path.join(self.examples_dir, example)
|
||||
return JSONFileController(filepath), ['']
|
||||
return JSONFileController(filepath), []
|
||||
else:
|
||||
filename = components[0]
|
||||
print(filename)
|
||||
filepath = path.join(self.schema_dir, filename)
|
||||
return JSONFileController(filepath), []
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# Copyright (c) 2015 Russell Sim <russell.sim@gmail.com>
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import unittest
|
||||
|
||||
from fairy_slipper.cmd import tempest_log
|
||||
|
||||
|
||||
SIMPLE_LOG = """Request (FlavorsV2TestJSON:setUpClass): 200 POST http://192.168.122.201:5000/v2.0/tokens
|
||||
Request - Headers: {}
|
||||
Body: None
|
||||
Response - Headers: {'status': '200', 'content-length': '2987', 'vary': 'X-Auth-Token', 'server': 'Apache/2.4.7 (Ubuntu)', 'connection': 'close', 'date': 'Sun, 13 Sep 2015 07:43:01 GMT', 'content-type': 'application/json', 'x-openstack-request-id': 'req-1'}
|
||||
Body: None
|
||||
Request (FlavorsV2TestJSON:test_get_flavor): 200 POST http://192.168.122.201:5000/v2.0/tokens
|
||||
Request - Headers: {}
|
||||
Body: None
|
||||
Response - Headers: {'status': '200', 'content-length': '2987', 'vary': 'X-Auth-Token', 'server': 'Apache/2.4.7 (Ubuntu)', 'connection': 'close', 'date': 'Sun, 13 Sep 2015 07:43:01 GMT', 'content-type': 'application/json', 'x-openstack-request-id': 'req-2'}
|
||||
Body: None
|
||||
""" # noqa
|
||||
|
||||
SIMPLE_LOG_BODY = """Request (FlavorsV2TestJSON:test_get_flavor): 200 GET http://192.168.122.201:8774/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1 0.117s
|
||||
Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': '<omitted>'}
|
||||
Body: None
|
||||
Response - Headers: {'status': '200', 'content-length': '430', 'content-location': 'http://192.168.122.201:8774/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1', 'x-compute-request-id': 'req-959a09e8-3628-419d-964a-1be4ca604232', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Sun, 13 Sep 2015 07:43:01 GMT', 'content-type': 'application/json'}
|
||||
Body: {"flavor": {"name": "m1.tiny", "links": [{"href": "http://192.168.122.201:8774/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1", "rel": "self"}, {"href": "http://192.168.122.201:8774/6b45254f6f7c44a1b65ddb8218932226/flavors/1", "rel": "bookmark"}], "ram": 512, "OS-FLV-DISABLED:disabled": false, "vcpus": 1, "swap": "", "os-flavor-access:is_public": true, "rxtx_factor": 1.0, "OS-FLV-EXT-DATA:ephemeral": 0, "disk": 1, "id": "1"}}
|
||||
""" # noqa
|
||||
|
||||
|
||||
class TestLogParser(unittest.TestCase):
|
||||
def test_simple_parse(self):
|
||||
result = tempest_log.parse_logfile(StringIO(SIMPLE_LOG))
|
||||
self.assertEqual(result, [
|
||||
({'url': '/v2.0/tokens',
|
||||
'service': 'identity',
|
||||
'headers': {},
|
||||
'body': None,
|
||||
'method': 'POST'},
|
||||
{'status_code': '200',
|
||||
'body': None,
|
||||
'headers': {'status': '200',
|
||||
'content-length': '2987',
|
||||
'date': 'Sun, 13 Sep 2015 07:43:01 GMT',
|
||||
'content-type': 'application/json',
|
||||
'x-openstack-request-id': 'req-1',
|
||||
'vary': 'X-Auth-Token',
|
||||
'connection': 'close',
|
||||
'server': 'Apache/2.4.7 (Ubuntu)'}}),
|
||||
({'url': '/v2.0/tokens',
|
||||
'service': 'identity',
|
||||
'headers': {},
|
||||
'body': None,
|
||||
'method': 'POST'},
|
||||
{'status_code': '200',
|
||||
'body': None,
|
||||
'headers': {'status': '200',
|
||||
'content-length': '2987',
|
||||
'date': 'Sun, 13 Sep 2015 07:43:01 GMT',
|
||||
'content-type': 'application/json',
|
||||
'x-openstack-request-id': 'req-2',
|
||||
'vary': 'X-Auth-Token',
|
||||
'connection': 'close',
|
||||
'server': 'Apache/2.4.7 (Ubuntu)'}})])
|
||||
|
||||
def test_body_parse(self):
|
||||
result = tempest_log.parse_logfile(StringIO(SIMPLE_LOG_BODY))
|
||||
|
||||
self.assertEqual(result, [
|
||||
({'url': '/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1',
|
||||
'headers': {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Auth-Token': '<omitted>'},
|
||||
'body': None,
|
||||
'method': 'GET',
|
||||
'service': 'compute'},
|
||||
{'body': '{\n "flavor": {\n "name": "m1.tiny", \n "links": [\n {\n "href": "http://192.168.122.201:8774/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1", \n "rel": "self"\n }, \n {\n "href": "http://192.168.122.201:8774/6b45254f6f7c44a1b65ddb8218932226/flavors/1", \n "rel": "bookmark"\n }\n ], \n "ram": 512, \n "OS-FLV-DISABLED:disabled": false, \n "vcpus": 1, \n "swap": "", \n "os-flavor-access:is_public": true, \n "rxtx_factor": 1.0, \n "OS-FLV-EXT-DATA:ephemeral": 0, \n "disk": 1, \n "id": "1"\n }\n}', # noqa
|
||||
'status_code': '200',
|
||||
'headers': {'status': '200', 'content-length': '430',
|
||||
'content-location': 'http://192.168.122.201:8774/v2.1/6b45254f6f7c44a1b65ddb8218932226/flavors/1', # noqa
|
||||
'x-openstack-nova-api-version': '2.1',
|
||||
'date': 'Sun, 13 Sep 2015 07:43:01 GMT',
|
||||
'vary': 'X-OpenStack-Nova-API-Version',
|
||||
'x-compute-request-id': 'req-959a09e8-3628-419d-964a-1be4ca604232', # noqa
|
||||
'content-type': 'application/json',
|
||||
'connection': 'close'}})])
|
|
@ -35,10 +35,6 @@ a.accordion-toggle:focus, a.accordion-toggle:hover {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.operation-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.operation-header h3{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
@ -58,7 +54,7 @@ a.accordion-toggle:focus, a.accordion-toggle:hover {
|
|||
|
||||
.swagger-method {
|
||||
vertical-align: top;
|
||||
top: 10px;
|
||||
top: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,3 +9,27 @@ angular.module('fairySlipper', [
|
|||
config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider.otherwise({redirectTo: '/'});
|
||||
}]);
|
||||
|
||||
|
||||
// Speed up calls to hasOwnProperty
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
function isEmpty(obj) {
|
||||
|
||||
// null and undefined are "empty"
|
||||
if (obj == null) return true;
|
||||
|
||||
// Assume if it has a length property with a non-zero value
|
||||
// that that property is correct.
|
||||
if (obj.length > 0) return false;
|
||||
if (obj.length === 0) return true;
|
||||
|
||||
// Otherwise, does it have any properties of its own?
|
||||
// Note that this doesn't handle
|
||||
// toString and valueOf enumeration bugs in IE < 9
|
||||
for (var key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,18 @@ angular.module('fairySlipper.browser', [
|
|||
|
||||
.directive('swaggerExample', ['$http', function($http) {
|
||||
function link(scope, element, attrs) {
|
||||
scope.$watch('triggerLoad', function(newValue, oldValue) {
|
||||
scope.language = 'json';
|
||||
|
||||
var mimes = [];
|
||||
angular.forEach(scope.source, function(value, key) {
|
||||
this.push(key);
|
||||
}, mimes);
|
||||
|
||||
if (!scope.mimetype) {
|
||||
scope.mimetype = mimes[0];
|
||||
}
|
||||
|
||||
var load = function(newValue, oldValue) {
|
||||
if (newValue && scope.source && ! scope.example) {
|
||||
$http.get('/doc/' + scope.swagger.info.service + '/' +
|
||||
scope.source[scope.mimetype].$ref +
|
||||
|
@ -47,7 +58,12 @@ angular.module('fairySlipper.browser', [
|
|||
scope.example = data;
|
||||
}
|
||||
});
|
||||
}});
|
||||
}};
|
||||
|
||||
scope.$watch('triggerLoad', load);
|
||||
if (scope.triggerLoad) {
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -142,6 +158,7 @@ angular.module('fairySlipper.browser', [
|
|||
}])
|
||||
|
||||
.controller('ByPathCtrl', ['$scope', '$http', '$routeParams', 'Service', function($scope, $http, $routeParams, Service) {
|
||||
$scope.isEmpty = isEmpty;
|
||||
Service.get({
|
||||
service: $routeParams.service,
|
||||
version: $routeParams.version
|
||||
|
@ -155,6 +172,7 @@ angular.module('fairySlipper.browser', [
|
|||
}])
|
||||
|
||||
.controller('ByTagCtrl', ['$scope', '$http', '$routeParams', 'Service', function($scope, $http, $routeParams, Service) {
|
||||
$scope.isEmpty = isEmpty;
|
||||
Service.get({
|
||||
service: $routeParams.service,
|
||||
version: $routeParams.version
|
||||
|
|
|
@ -5,24 +5,23 @@
|
|||
</div>
|
||||
|
||||
<div ng-repeat="operations in paths | orderBy: '$key'">
|
||||
<h2>
|
||||
<h3>
|
||||
<swagger-path path="operations.$key"
|
||||
parameters="operations[0].parameters">
|
||||
</swagger-path>
|
||||
</h2>
|
||||
</h3>
|
||||
<accordion>
|
||||
<accordion-group ng-repeat="operation in operations">
|
||||
<accordion-group ng-repeat="operation in operations"
|
||||
is-open="operation_open">
|
||||
<accordion-heading>
|
||||
<div class="operation-header">
|
||||
<div class="operation-method">
|
||||
<swagger-method method="operation.method"></swagger-method>
|
||||
</div>
|
||||
<div class="operation-title">
|
||||
<h3>
|
||||
{{ operation.title }}
|
||||
<br/>
|
||||
<small>{{ operation.summary }}</small>
|
||||
</h3>
|
||||
{{ operation.title }}
|
||||
<br/>
|
||||
<small>{{ operation.summary }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</accordion-heading>
|
||||
|
@ -36,7 +35,7 @@
|
|||
</tab>
|
||||
<tab heading="Details">
|
||||
<div ng-if="parameters.header">
|
||||
<h4>Headers</h4>
|
||||
<h5>Headers</h5>
|
||||
<dl>
|
||||
<dt ng-repeat-start="parameter in parameters.header">{{parameter.name}}
|
||||
</dt>
|
||||
|
@ -44,7 +43,7 @@
|
|||
</dl>
|
||||
</div>
|
||||
<div ng-if="parameters.query">
|
||||
<h4>URL Parameters</h4>
|
||||
<h5>URL Parameters</h5>
|
||||
<dl>
|
||||
<dt ng-repeat-start="parameter in parameters.query">{{parameter.name}}
|
||||
</dt>
|
||||
|
@ -52,27 +51,35 @@
|
|||
</dl>
|
||||
</div>
|
||||
<div ng-if="parameters.body">
|
||||
<h4>Request Schema</h4>
|
||||
<h5>Request Schema</h5>
|
||||
<swagger-schema swagger="swagger" parameters="parameters.body"></swagger-schema>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Request</h4>
|
||||
<swagger-example ng-if="!isEmpty(operation.examples)"
|
||||
swagger="swagger"
|
||||
src="operation.examples"
|
||||
trigger-load="operation_open">
|
||||
</swagger-example>
|
||||
<div ng-if="isEmpty(response.examples)">
|
||||
No request recorded.
|
||||
</div>
|
||||
<h4>Responses</h4>
|
||||
<accordion close-others="true">
|
||||
<accordion-group ng-repeat="(status_code, response) in operation.responses"
|
||||
is-disabled="!response.examples['application/json']"
|
||||
is-disabled="isEmpty(response.examples)"
|
||||
is-open="status.open">
|
||||
<accordion-heading>
|
||||
{{ status_code }}: {{ response.description }}
|
||||
<i ng-if="response.examples['application/json']"
|
||||
<i ng-if="!isEmpty(response.examples)"
|
||||
class="pull-right glyphicon"
|
||||
ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
|
||||
</accordion-heading>
|
||||
<swagger-example swagger="swagger"
|
||||
src="response.examples"
|
||||
mimetype="'application/json'"
|
||||
trigger-load="status.open">
|
||||
</swagger-example>
|
||||
</accordion-group>
|
||||
|
|
|
@ -5,21 +5,20 @@
|
|||
</div>
|
||||
|
||||
<div ng-repeat="tag in swagger.tags">
|
||||
<h2>{{ tag.summary }}</h2>
|
||||
<h3>{{ tag.summary }}</h3>
|
||||
<div marked="tag.description"></div>
|
||||
<accordion>
|
||||
<accordion-group ng-repeat="operation in operations[tag.name]">
|
||||
<accordion-group ng-repeat="operation in operations[tag.name]"
|
||||
is-open="operation_open">
|
||||
<accordion-heading>
|
||||
<div class="operation-header">
|
||||
<div class="operation-method">
|
||||
<swagger-method method="operation.method"></swagger-method>
|
||||
</div>
|
||||
<div class="operation-title">
|
||||
<h3>
|
||||
{{ operation.title }}
|
||||
<br/>
|
||||
<small>{{ operation.summary }}</small>
|
||||
</h3>
|
||||
{{ operation.title }}
|
||||
<br/>
|
||||
<small>{{ operation.summary }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</accordion-heading>
|
||||
|
@ -38,7 +37,7 @@
|
|||
</tab>
|
||||
<tab heading="Details">
|
||||
<div ng-if="parameters.header">
|
||||
<h4>Headers</h4>
|
||||
<h5>Headers</h5>
|
||||
<dl>
|
||||
<dt ng-repeat-start="parameter in parameters.header">{{parameter.name}}
|
||||
</dt>
|
||||
|
@ -46,7 +45,7 @@
|
|||
</dl>
|
||||
</div>
|
||||
<div ng-if="parameters.query">
|
||||
<h4>URL Parameters</h4>
|
||||
<h5>URL Parameters</h5>
|
||||
<dl>
|
||||
<dt ng-repeat-start="parameter in parameters.query">{{parameter.name}}
|
||||
</dt>
|
||||
|
@ -54,27 +53,35 @@
|
|||
</dl>
|
||||
</div>
|
||||
<div ng-if="parameters.body">
|
||||
<h4>Request Schema</h4>
|
||||
<h5>Request Schema</h5>
|
||||
<swagger-schema swagger="swagger" parameters="parameters.body"></swagger-schema>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Request</h4>
|
||||
<swagger-example ng-if="!isEmpty(operation.examples)"
|
||||
swagger="swagger"
|
||||
src="operation.examples"
|
||||
trigger-load="operation_open">
|
||||
</swagger-example>
|
||||
<div ng-if="isEmpty(response.examples)">
|
||||
No request recorded.
|
||||
</div>
|
||||
<h4>Responses</h4>
|
||||
<accordion close-others="true">
|
||||
<accordion-group ng-repeat="(status_code, response) in operation.responses"
|
||||
is-disabled="!response.examples['application/json']"
|
||||
is-disabled="isEmpty(response.examples)"
|
||||
is-open="status.open">
|
||||
<accordion-heading>
|
||||
{{ status_code }}: {{ response.description }}
|
||||
<i ng-if="response.examples['application/json']"
|
||||
<i ng-if="!isEmpty(response.examples)"
|
||||
class="pull-right glyphicon"
|
||||
ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
|
||||
</accordion-heading>
|
||||
<swagger-example swagger="swagger"
|
||||
src="response.examples"
|
||||
mimetype="'application/json'"
|
||||
trigger-load="status.open">
|
||||
</swagger-example>
|
||||
</accordion-group>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<div hljs source="example"></div>
|
||||
<div hljs source="example" language="{{ language }}"></div>
|
||||
|
|
|
@ -29,6 +29,7 @@ console_scripts =
|
|||
fairy-slipper-docbkx-to-json = fairy_slipper.cmd.docbkx_to_json:main
|
||||
fairy-slipper-swagger-to-rst = fairy_slipper.cmd.swagger_to_rst:main
|
||||
fairy-slipper-wadl-to-swagger = fairy_slipper.cmd.wadl_to_swagger:main
|
||||
fairy-slipper-tempest-log = fairy_slipper.cmd.tempest_log:main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
|
Loading…
Reference in New Issue