praxis 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -1
- data/Gemfile +2 -1
- data/README.md +21 -27
- data/lib/api_browser/app/index.html +3 -3
- data/lib/api_browser/app/js/app.js +23 -3
- data/lib/api_browser/app/js/controllers/action.js +33 -21
- data/lib/api_browser/app/js/controllers/controller.js +3 -25
- data/lib/api_browser/app/js/controllers/menu.js +61 -51
- data/lib/api_browser/app/js/controllers/trait.js +10 -0
- data/lib/api_browser/app/js/controllers/type.js +8 -5
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +9 -2
- data/lib/api_browser/app/js/directives/menu_item.js +59 -0
- data/lib/api_browser/app/js/directives/readable_list.js +87 -0
- data/lib/api_browser/app/js/directives/url.js +16 -0
- data/lib/api_browser/app/js/factories/Configuration.js +1 -2
- data/lib/api_browser/app/js/factories/Documentation.js +49 -7
- data/lib/api_browser/app/js/factories/PageInfo.js +9 -0
- data/lib/api_browser/app/js/factories/normalize_attributes.js +1 -2
- data/lib/api_browser/app/js/factories/template_for.js +9 -4
- data/lib/api_browser/app/sass/modules/_sidebar.scss +54 -15
- data/lib/api_browser/app/sass/praxis.scss +4 -0
- data/lib/api_browser/app/views/action.html +72 -41
- data/lib/api_browser/app/views/builtin/field-selector.html +24 -0
- data/lib/api_browser/app/views/controller.html +9 -10
- data/lib/api_browser/app/views/directives/menu_item.html +8 -0
- data/lib/api_browser/app/views/directives/url.html +3 -0
- data/lib/api_browser/app/views/layout.html +2 -2
- data/lib/api_browser/app/views/menu.html +8 -14
- data/lib/api_browser/app/views/navbar.html +1 -1
- data/lib/api_browser/app/views/trait.html +13 -0
- data/lib/api_browser/app/views/type/details.html +1 -1
- data/lib/api_browser/app/views/type.html +1 -1
- data/lib/api_browser/app/views/types/embedded/field-selector.html +13 -0
- data/lib/api_browser/app/views/types/label/primitive.html +1 -1
- data/lib/api_browser/app/views/types/standalone/array.html +3 -0
- data/lib/praxis/action_definition.rb +15 -2
- data/lib/praxis/collection.rb +17 -5
- data/lib/praxis/controller.rb +12 -3
- data/lib/praxis/docs/generator.rb +11 -7
- data/lib/praxis/extensions/field_expansion.rb +59 -0
- data/lib/praxis/extensions/field_selection/field_selector.rb +125 -0
- data/lib/praxis/extensions/field_selection.rb +10 -0
- data/lib/praxis/extensions/mapper_selectors.rb +16 -0
- data/lib/praxis/extensions/rendering.rb +43 -0
- data/lib/praxis/links.rb +1 -0
- data/lib/praxis/media_type.rb +87 -3
- data/lib/praxis/media_type_collection.rb +1 -1
- data/lib/praxis/media_type_identifier.rb +6 -1
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +29 -10
- data/lib/praxis/restful_doc_generator.rb +11 -8
- data/lib/praxis/tasks/api_docs.rb +6 -5
- data/lib/praxis/types/multipart_array.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -0
- data/praxis.gemspec +4 -3
- data/spec/api_browser/factories/configuration_spec.js +32 -0
- data/spec/api_browser/factories/documentation_spec.js +75 -25
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -5
- data/spec/praxis/{types/collection_spec.rb → collection_spec.rb} +36 -23
- data/spec/praxis/extensions/field_expansion_spec.rb +96 -0
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +92 -0
- data/spec/praxis/extensions/rendering_spec.rb +63 -0
- data/spec/praxis/links_spec.rb +6 -0
- data/spec/praxis/media_type_collection_spec.rb +0 -1
- data/spec/praxis/media_type_identifier_spec.rb +15 -1
- data/spec/praxis/media_type_spec.rb +101 -3
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +33 -24
- data/spec/praxis/request_stages/request_stage_spec.rb +1 -1
- data/spec/praxis/types/multipart_array_spec.rb +14 -4
- data/spec/spec_app/app/controllers/instances.rb +6 -1
- data/spec/spec_app/config/environment.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +1 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/spec_media_types.rb +224 -1
- metadata +50 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c3c0d0844cabeb646b294a5a91b3c1fff24c848
|
4
|
+
data.tar.gz: edcb41f6707ec368f6a87f0f6273bc6aef558b7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afecbf0a00ee48c51eb9c49a1e0867431569e9136ef74a1e852800d143f5f672d276fa5ba80c1339629faf23d518d50067693ddb4f05702925fa4fb2071131f1
|
7
|
+
data.tar.gz: 6348cd21d00aa34085870a0b8af94a4939b31da4d00c962ab4dd49cb316a699a0412082db7e104c18bfb423a731000f04c86422d228be7ecb2ee156573d0afda
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,63 @@
|
|
1
|
-
#
|
1
|
+
# Praxis Changelog
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 0.19.0
|
6
|
+
|
7
|
+
* Handle loading empty `MediaTypeIdentifier` values (to return `nil`)
|
8
|
+
* Doc browser now displays examples for action responses.
|
9
|
+
* Added `Controller#media_type` helper that returns the `media_type` defined
|
10
|
+
for the current response, or defaults to the one defined by the controller's
|
11
|
+
resource definition.
|
12
|
+
* Added assorted extensions, all under the `Praxis::Extensions` module:
|
13
|
+
* `FieldSelection` adds a new type, `Praxis::Extensions::FieldSelection::FieldSelector`
|
14
|
+
that wraps the `Attributor::FieldSelector` type and improves the definition
|
15
|
+
of parameters for a set of fields. Must be required explicitly from
|
16
|
+
'praxis/extensions/field_selection'.
|
17
|
+
* The parsed set of fields will be available as the `fields` accessor of
|
18
|
+
the loaded value.
|
19
|
+
* For example, to define a parameter that should take a set of fields
|
20
|
+
for a `Person` media type, you would define a `:fields` attribute in the
|
21
|
+
params like: `attribute :fields, FieldSelector.for(Person)`. The parsed
|
22
|
+
fields in the request would then be available with
|
23
|
+
`request.params.fields.fields`.
|
24
|
+
* `Rendering` adds `render` and `display` helper methods to controllers to
|
25
|
+
reduce common boilerplate in producing rendered representations of media types
|
26
|
+
and setting response "Content-Type" headers.
|
27
|
+
* `Controller#render(object, include_nil: false)` loads `object` into the
|
28
|
+
the current applicable `MediaType` (as from `Controller#media_type`) and
|
29
|
+
renders it using the fields provided by `Controller#expanded_fields` (from the
|
30
|
+
`FieldExpansion` extension).
|
31
|
+
* `Controller#display(object, include_nil: false)` calls `render` (above) with
|
32
|
+
`object`, assigns the result to the current `response.body`, sets the
|
33
|
+
response's "Content-Type" header to the appropriate MediaType identifier,
|
34
|
+
and returns the response.
|
35
|
+
* To use this extension, include it in a controller with
|
36
|
+
`include Praxis::Extensions::Rendering`.
|
37
|
+
* `MapperSelectors` adds `Controller#set_selectors`, which sets selectors
|
38
|
+
in the controller's `identity_map` to ensure any fields and associations
|
39
|
+
necessary to render the `:view` and/or `:fields` params specified in the
|
40
|
+
request are loaded for a given model when `identity_map.load(model)` is called.
|
41
|
+
* To use this extension, include it in a controller with
|
42
|
+
`include Praxis::Extensions::MapperSelectors`, and define `before`
|
43
|
+
callbacks on relevant actions that call `set_selectors`. For example:
|
44
|
+
`before actions: [:index, :show] { |controller| controller.set_selectors }`
|
45
|
+
* `FieldExpansion` provides a `Controller#expanded_fields` helper for
|
46
|
+
processing `:view` and/or `:fields` params to determine the final set fields
|
47
|
+
necessary to handle the request.
|
48
|
+
* Note: This is primarily an internal extension used by the `MapperSelectors`
|
49
|
+
and `Rendering` extensions, and is automatically included by them.
|
50
|
+
* A slew of Doc browser improvements:
|
51
|
+
* Now uses the new JSON format for responses.
|
52
|
+
* Traits now get exposed in the doc browser.
|
53
|
+
* Now displays examples for requesting actions.
|
54
|
+
* Now correctly displays top-level collections in action payloads.
|
55
|
+
* Has improved scrolling for the sidebar.
|
56
|
+
* Displays more detailed HTML titles.
|
57
|
+
* Has been switched back to having a separate page per action, however actions are now shown in the sidebar.
|
58
|
+
* Will now display multiply nested resources in a proper hierarchy.
|
59
|
+
* Fix doc generator to only output versions in index for which we have resources (i.e. some can be nodoc!)
|
60
|
+
|
5
61
|
## 0.18.1
|
6
62
|
|
7
63
|
* Fix Doc Browser regression, which would not show the schema in the Resource Definition home page.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,23 @@
|
|
1
|
-
# Praxis [![TravisCI][travis-img-url]][travis-ci-url]
|
1
|
+
# Praxis [![TravisCI][travis-img-url]][travis-ci-url] [![Coverage Status][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url]
|
2
2
|
|
3
3
|
[travis-img-url]: https://travis-ci.org/rightscale/praxis.svg?branch=master
|
4
4
|
[travis-ci-url]:https://travis-ci.org/rightscale/praxis
|
5
|
+
[coveralls-img-url]:https://coveralls.io/repos/rightscale/praxis/badge.svg?branch=master&service=github
|
6
|
+
[coveralls-url]:https://coveralls.io/github/rightscale/praxis?branch=master
|
7
|
+
[gemnasium-img-url]:https://gemnasium.com/rightscale/praxis.svg
|
8
|
+
[gemnasium-url]:https://gemnasium.com/rightscale/praxis
|
5
9
|
|
6
|
-
Praxis is a framework
|
10
|
+
Praxis is a framework for both _designing_ and _implementing_ APIs.
|
7
11
|
|
8
|
-
|
9
|
-
|
12
|
+
An important part of the framework is geared towards the _design_ aspect of building an API. This functionality empowers architects with tools to design every last aspect of their API, resulting in a complete, web-browsable documentation, which includes automatic generation of examples for resources, parameters, headers, etc...as well as requests and responses for the supported encodings. The design process is iterative, and flows from defining new resources, parameters, etc...to reviewing the resulting docs (usually with some of the potential clients of the API), back to updating the design based on feedback, or expanding it with more resources. The design language (i.e. DSL) of Praxis follows a clean 'ruby-type-syntax' and its final outcome is to generates an output that is both a set of schema documents as well as a web-based API browser (driven by those schemas). This allows Praxis to design APIs that can potentially be implemented in any language.
|
13
|
+
|
14
|
+
Another important part of the framework is geared towards helping in the _implementation_ of the API service. In particular, Praxis provides help to Ruby developers for building a service conforming to the designed API. Aside from Ruby there is also sister-project called [Goa](http://goa.design/) that assists in implementing Praxis-like design API using Golang. Since the API design generates schema files very similar in nature to other API document formats like (Swagger, Google Discovery, RAML, etc...) supporting other implementation languages could be easily accomplished in the future by building converters to/from them.
|
15
|
+
|
16
|
+
The part of the framework that helps with the ruby service implementation takes an approach that is different from other existing ruby (micro)frameworks such as [Grape](http://www.ruby-grape.org), [Sinatra](https://github.com/sinatra/sinatra), [Scorched](http://scorchedrb.com/), [Lotus](http://lotusrb.org/) or even [RailsAPI](https://github.com/rails-api/rails-api) (now part of Rails). Instead of being developer-centric, it takes an integrated approach treating both designers and implementors as first class citizens throughout the complete API building process. With Praxis you create an API by iterating through the design, review and implementation phases. While Praxis can help Ruby developers in a lot of aspects involved in building a service, the framework is completely componentized as to allow developers to pick and choose which parts to use, which ones not to use, and which other technologies to integrate with. The framework provides help in many areas, for example: all aspects of request and response validation, automatic type-coercion, consistent error-responses, routing and url generation, advanced template/media-type definition and rendering, domain-modeling, optional database ORM (for high-perfomance large datasets), DB integration (with an efficient identityMap), a plugin and extensible framework to easily hook into, available integrations such as newrelic, statsd, etc...
|
17
|
+
|
18
|
+
There is a long list of benefits that come from using Praxis. From those, here are a couple of the salient themes:
|
19
|
+
* Designing APIs with Praxis result in a very precise, consistent and beautiful API documentation that it is apt for both human (web browsable) or machine consumption (JSON spec files).
|
20
|
+
* Building Ruby APIs with Praxis will result in much faster developer times, and more importantly with a resulting service that will ensure that its implementation is always consistent with its design, and it is enforced at all times.
|
10
21
|
|
11
22
|
## Quickstart
|
12
23
|
```bash
|
@@ -14,7 +25,7 @@ and for all, stop worrying about re-generating server code and keeping it in syn
|
|
14
25
|
gem install praxis
|
15
26
|
|
16
27
|
# Generate a praxis application named my-app in ./my-app
|
17
|
-
praxis
|
28
|
+
praxis example my-app
|
18
29
|
|
19
30
|
# Run it!
|
20
31
|
cd my-app
|
@@ -22,29 +33,8 @@ bundle
|
|
22
33
|
rackup
|
23
34
|
```
|
24
35
|
|
25
|
-
|
26
|
-
Praxis is a practical implementation of a few guiding principles. In part:
|
27
|
-
|
28
|
-
### REST APIs should be consistent
|
29
|
-
REST APIs should follow consistent design in patterns and action semantics.
|
30
|
-
This includes software patterns for creating applications like code
|
31
|
-
organization; application patterns like logging, middleware, query filtering,
|
32
|
-
and application bootstrapping; and API routing structures and REST verb
|
33
|
-
semantics for exposed resources.
|
36
|
+
Or better yet, checkout a simple, but functional [blog example app](https://github.com/rightscale/praxis-example-app) which showcases a few of the main design and implementation aspects that Praxis has to offer.
|
34
37
|
|
35
|
-
### Apps should focus on real business logic
|
36
|
-
Applications should be able to focus on application logic, avoiding most of the
|
37
|
-
boilerplate code required to build API services.
|
38
|
-
|
39
|
-
### API design should be separate from implementation
|
40
|
-
Applications should maintain separation of concerns between API design and
|
41
|
-
implementation. An API designer should be able to fully construct an API
|
42
|
-
skeleton without writing a single line of application code.
|
43
|
-
|
44
|
-
### APIs must have great documentation
|
45
|
-
It should be possible to generate consistent and detailed documentation. This
|
46
|
-
should be done by inspecting real code, to avoid relying on humans to do a good
|
47
|
-
job keeping the docs and the code in sync everytime they make code changes.
|
48
38
|
|
49
39
|
## Mailing List
|
50
40
|
Join our Google Groups for discussion, support and announcements.
|
@@ -54,6 +44,10 @@ Join our Google Groups for discussion, support and announcements.
|
|
54
44
|
* [praxis-development](http://groups.google.com/d/forum/praxis-development) (discussion about the
|
55
45
|
development of Praxis itself)
|
56
46
|
|
47
|
+
Join our slack support and general announcements channel for on-the-spot answers to your questions:
|
48
|
+
* To join our slack chat please go to: http://praxis-framework.herokuapp.com and sign in for an account.
|
49
|
+
* Once you have an account, hop onto the chat at http://praxis-framework.slack.com
|
50
|
+
|
57
51
|
And follow us on twitter: [@praxisapi](http://twitter.com/praxisapi)
|
58
52
|
|
59
53
|
## Contributions
|
@@ -1,16 +1,16 @@
|
|
1
1
|
<!doctype html>
|
2
|
-
<html>
|
2
|
+
<html ng-app="DocBrowser">
|
3
3
|
<head>
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
-
<title>API Browser</title>
|
6
|
+
<title ng-bind="subtitle ? title + ' – ' + subtitle : title">API Browser</title>
|
7
7
|
<meta name="description" content="">
|
8
8
|
<meta name="viewport" content="width=device-width">
|
9
9
|
<!-- bower:css -->
|
10
10
|
<!-- endbower -->
|
11
11
|
<link rel="stylesheet" href="css/main.css">
|
12
12
|
</head>
|
13
|
-
<body
|
13
|
+
<body>
|
14
14
|
<div ui-view=""></div>
|
15
15
|
<!-- build:js({.tmp,app}) scripts/praxis.js -->
|
16
16
|
<!-- bower:js -->
|
@@ -1,6 +1,9 @@
|
|
1
1
|
var app = angular.module('PraxisDocBrowser', ['ui.router', 'ui.bootstrap', 'ngSanitize']);
|
2
2
|
|
3
|
-
app.config(function ($stateProvider, $urlRouterProvider) {
|
3
|
+
app.config(function ($stateProvider, $urlRouterProvider, $uiViewScrollProvider) {
|
4
|
+
|
5
|
+
$uiViewScrollProvider.useAnchorScroll();
|
6
|
+
|
4
7
|
$urlRouterProvider
|
5
8
|
.otherwise('/');
|
6
9
|
|
@@ -22,7 +25,24 @@ app.config(function ($stateProvider, $urlRouterProvider) {
|
|
22
25
|
url: '/:version/type/:type',
|
23
26
|
templateUrl: 'views/type.html',
|
24
27
|
controller: 'TypeCtrl'
|
25
|
-
})
|
26
|
-
|
28
|
+
})
|
29
|
+
.state('root.action', {
|
30
|
+
url: '/:version/controller/:controller/:action',
|
31
|
+
templateUrl: 'views/action.html',
|
32
|
+
controller: 'ActionCtrl'
|
33
|
+
})
|
34
|
+
.state('root.trait', {
|
35
|
+
url: '/:version/trait/:trait',
|
36
|
+
templateUrl: 'views/trait.html',
|
37
|
+
controller: 'TraitCtrl'
|
38
|
+
})
|
39
|
+
.state('root.builtin', {
|
40
|
+
abstract: true,
|
41
|
+
url: '/builtin',
|
42
|
+
template: '<ui-view/>'
|
43
|
+
})
|
44
|
+
.state('root.builtin.field-selector', {
|
45
|
+
url: '/field-selector',
|
46
|
+
templateUrl: 'views/builtin/field-selector.html'
|
27
47
|
});
|
28
48
|
});
|
@@ -1,32 +1,44 @@
|
|
1
|
-
app.controller('ActionCtrl', function($scope, $stateParams, Documentation, normalizeAttributes) {
|
1
|
+
app.controller('ActionCtrl', function($scope, $stateParams, Documentation, normalizeAttributes, PageInfo) {
|
2
2
|
$scope.controllerName = $stateParams.controller;
|
3
3
|
$scope.actionName = $stateParams.action;
|
4
4
|
$scope.apiVersion = $stateParams.version;
|
5
5
|
|
6
|
+
Documentation.controller($stateParams.version, $stateParams.controller).then(function(response) {
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
if (
|
11
|
-
|
8
|
+
$scope.controller = response;
|
9
|
+
PageInfo.title = $scope.controller.display_name + ' » ' + $scope.actionName;
|
10
|
+
$scope.action = _.find(response.actions, function(action) { return action.name === $scope.actionName; });
|
11
|
+
if (!$scope.action) {
|
12
|
+
$scope.error = true;
|
13
|
+
return;
|
12
14
|
}
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
};
|
21
|
-
$scope.responses.push(response);
|
15
|
+
// Extract the example and attach it to each attribute
|
16
|
+
_.forEach(['headers', 'params', 'payload'], function(n) {
|
17
|
+
var set = $scope.action[n];
|
18
|
+
if (set) {
|
19
|
+
normalizeAttributes(set, set.type.attributes);
|
20
|
+
}
|
21
|
+
});
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
response.
|
26
|
-
|
23
|
+
$scope.responses = [];
|
24
|
+
_.forEach($scope.action.responses, function(response, name) {
|
25
|
+
response.name = name;
|
26
|
+
response.options = {
|
27
|
+
headers: response.headers
|
27
28
|
};
|
28
|
-
|
29
|
-
|
29
|
+
|
30
|
+
response.numExamples = _.keys(_.get(response, 'payload.examples')).length;
|
31
|
+
|
32
|
+
$scope.responses.push(response);
|
33
|
+
|
34
|
+
if(response.parts_like) {
|
35
|
+
response.parts_like.isMultipart = true;
|
36
|
+
response.parts_like.options = {
|
37
|
+
headers: response.parts_like.headers
|
38
|
+
};
|
39
|
+
$scope.responses.push(response.parts_like);
|
40
|
+
}
|
41
|
+
});
|
30
42
|
});
|
31
43
|
|
32
44
|
$scope.hasResponses = function() {
|
@@ -1,32 +1,10 @@
|
|
1
|
-
app.controller('ControllerCtrl', function($scope, $stateParams, Documentation
|
1
|
+
app.controller('ControllerCtrl', function($scope, $stateParams, Documentation) {
|
2
2
|
$scope.controllerName = $stateParams.controller;
|
3
3
|
$scope.apiVersion = $stateParams.version;
|
4
4
|
|
5
|
-
Documentation.
|
6
|
-
$scope.controller = response
|
5
|
+
Documentation.controller($stateParams.version, $stateParams.controller).then(function(response) {
|
6
|
+
$scope.controller = response;
|
7
7
|
}, function() {
|
8
8
|
$scope.error = true;
|
9
9
|
});
|
10
|
-
|
11
|
-
$scope.$on('$stateChangeSuccess', function(e, state, params) {
|
12
|
-
if (state.name == 'root.controller.action') {
|
13
|
-
(function scrollTo(id, time) {
|
14
|
-
if (angular.element('#' + id).length > 0) {
|
15
|
-
if (time > 20) {
|
16
|
-
$timeout(function() {
|
17
|
-
$anchorScroll.yOffset = angular.element('.header .navbar');
|
18
|
-
$anchorScroll(id);
|
19
|
-
}, 400);
|
20
|
-
} else {
|
21
|
-
$anchorScroll.yOffset = angular.element('.header .navbar');
|
22
|
-
$anchorScroll(id);
|
23
|
-
}
|
24
|
-
} else {
|
25
|
-
$timeout(function() {
|
26
|
-
scrollTo(id, 2 * time);
|
27
|
-
}, time);
|
28
|
-
}
|
29
|
-
})('action-' + params.action, 10);
|
30
|
-
}
|
31
|
-
});
|
32
10
|
});
|
@@ -2,53 +2,70 @@ app.controller('MenuCtrl', function($scope, $state, Documentation) {
|
|
2
2
|
var parentLookup = {};
|
3
3
|
$scope.versions = [];
|
4
4
|
$scope.resources = {};
|
5
|
-
$scope.
|
5
|
+
$scope.schemas = {};
|
6
|
+
$scope.traits = {};
|
6
7
|
$scope.selectedVersion = '';
|
7
8
|
$scope.currentType = '';
|
8
9
|
$scope.active = {};
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
Documentation.versions().then(function(versions) {
|
12
|
+
$scope.versions = versions;
|
13
|
+
_.each(versions, function(version) {
|
14
|
+
Documentation.items(version).then(function(items) {
|
15
|
+
var resources = $scope.resources[version] = [];
|
16
|
+
var schemas = $scope.schemas[version] = [];
|
17
|
+
var traits = $scope.traits[version] = [];
|
18
|
+
var children = [];
|
19
|
+
_.each(items.resources, function(item, name) {
|
20
|
+
var link = { name: item.display_name, stateRef: '' };
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
22
|
+
parentLookup[name] = link;
|
23
|
+
link.stateRef = $state.href('root.controller', { version: version, controller: name });
|
24
|
+
link.id = name;
|
25
|
+
|
26
|
+
link.actions = _.map(item.actions, function(action) {
|
27
|
+
var actionLink = { name: action.name, stateRef: '' };
|
28
|
+
parentLookup[name + '_action_' + action.name] = actionLink;
|
29
|
+
actionLink.parent = name;
|
30
|
+
actionLink.stateRef = $state.href('root.action', { version: version, controller: name, action: action.name });
|
31
|
+
actionLink.id = name + '_action_' + action.name;
|
32
|
+
actionLink.isAction = true;
|
33
|
+
actionLink.parentRef = link;
|
34
|
+
return actionLink;
|
35
|
+
});
|
36
|
+
link.childResources = [];
|
37
|
+
if (item.parent) {
|
38
|
+
link.parent = item.parent;
|
39
|
+
children.push(link);
|
40
|
+
} else {
|
41
|
+
resources.push(link);
|
42
|
+
}
|
43
|
+
});
|
44
|
+
_.each(items.schemas, function(item, name) {
|
45
|
+
var link = { name: item.display_name, stateRef: '' };
|
46
|
+
link.stateRef = $state.href('root.type', { version: version, type: item.id });
|
47
|
+
link.id = name;
|
48
|
+
schemas.push(link);
|
49
|
+
});
|
50
|
+
_.each(items.traits, function(item, name) {
|
51
|
+
var link = { name: name, stateRef: '' };
|
52
|
+
link.stateRef = $state.href('root.trait', { version: version, trait: name });
|
53
|
+
link.id = name;
|
54
|
+
traits.push(link);
|
55
|
+
});
|
56
|
+
_.each(children, function(link) {
|
57
|
+
if (parentLookup[link.parent]) {
|
58
|
+
link.parentRef = parentLookup[link.parent];
|
59
|
+
parentLookup[link.parent].childResources.push(link);
|
60
|
+
} else {
|
61
|
+
throw 'No parent resource found';
|
62
|
+
}
|
63
|
+
});
|
46
64
|
});
|
47
65
|
});
|
48
66
|
var numeralVersions = _.filter($scope.versions, function(n) { return !isNaN(parseFloat(n)); })
|
49
67
|
.sort(function(a,b) { return parseFloat(b) - parseFloat(a); });
|
50
68
|
$scope.selectedVersion = $state.params.version || numeralVersions[0] || $scope.versions[0];
|
51
|
-
|
52
69
|
});
|
53
70
|
|
54
71
|
$scope.select = function(version) {
|
@@ -59,25 +76,18 @@ app.controller('MenuCtrl', function($scope, $state, Documentation) {
|
|
59
76
|
return $scope.resources[$scope.selectedVersion];
|
60
77
|
};
|
61
78
|
|
62
|
-
$scope.
|
63
|
-
return $scope.
|
79
|
+
$scope.availableSchemas = function() {
|
80
|
+
return $scope.schemas[$scope.selectedVersion];
|
64
81
|
};
|
65
82
|
|
66
|
-
function
|
67
|
-
|
68
|
-
|
69
|
-
return grandfatherType(self.parent);
|
70
|
-
}
|
71
|
-
return id;
|
72
|
-
}
|
83
|
+
$scope.availableTraits = function() {
|
84
|
+
return $scope.traits[$scope.selectedVersion];
|
85
|
+
};
|
73
86
|
|
74
87
|
$scope.$on('$stateChangeSuccess', function(e, state, params) {
|
75
88
|
if (params.version) $scope.selectedVersion = params.version;
|
76
|
-
$scope.
|
77
|
-
menuPromise.then(function() {
|
78
|
-
$scope.selectedGrandfatherType = params.controller && grandfatherType(params.controller);
|
79
|
-
});
|
80
|
-
$scope.active.resources = state.name !== 'root.type';
|
89
|
+
$scope.active.resources = state.name !== 'root.type' && state.name !== 'root.trait';
|
81
90
|
$scope.active.schemas = state.name === 'root.type';
|
91
|
+
$scope.active.traits = state.name === 'root.trait';
|
82
92
|
});
|
83
93
|
});
|
@@ -0,0 +1,10 @@
|
|
1
|
+
app.controller('TraitCtrl', function ($scope, $stateParams, Documentation) {
|
2
|
+
$scope.traitName = $stateParams.trait;
|
3
|
+
$scope.apiVersion = $stateParams.version;
|
4
|
+
|
5
|
+
Documentation.trait($stateParams.version, $scope.traitName).then(function(data) {
|
6
|
+
$scope.trait = data;
|
7
|
+
}, function() {
|
8
|
+
$scope.error = true;
|
9
|
+
});
|
10
|
+
});
|
@@ -4,16 +4,19 @@
|
|
4
4
|
$scope.controllers = [];
|
5
5
|
$scope.views = [];
|
6
6
|
|
7
|
-
Documentation.
|
8
|
-
$scope.type =
|
9
|
-
$scope.views = _(
|
7
|
+
Documentation.type($stateParams.version, $scope.typeId).then(function(data) {
|
8
|
+
$scope.type = data;
|
9
|
+
$scope.views = _(data.views)
|
10
10
|
.map(function(view, name) { return _.extend(view, { name: name }); })
|
11
11
|
.select(function(view) { return view.name !== 'master'; })
|
12
12
|
.value();
|
13
13
|
normalizeAttributes($scope.type, $scope.type.attributes);
|
14
14
|
|
15
|
-
Documentation.
|
16
|
-
$scope.controllers = _.select(response
|
15
|
+
Documentation.items($scope.apiVersion).then(function(response) {
|
16
|
+
$scope.controllers = _.select(response.resources, function(item, id) {
|
17
|
+
item.id = id;
|
18
|
+
return item.media_type.id == $scope.type.id;
|
19
|
+
});
|
17
20
|
});
|
18
21
|
}, function() {
|
19
22
|
$scope.error = true;
|
@@ -10,8 +10,15 @@ app.directive('fixedIfFits', function($timeout) {
|
|
10
10
|
var height = _(element.find('.tab-content .tab-pane').get()).map(function(el) {
|
11
11
|
return angular.element(el).height();
|
12
12
|
}).max() + element.offset().top;
|
13
|
-
|
14
|
-
|
13
|
+
var mq = window.matchMedia('(min-width: 768px)');
|
14
|
+
if (height < $(window).height() && mq.matches) {
|
15
|
+
var padding = 20;
|
16
|
+
element[0].style.width = (element.width() + padding) + 'px';
|
17
|
+
element[0].style.paddingRight = padding + 'px';
|
18
|
+
var navbarHeight = '' + ($('.navbar').height() || 60) + 'px';
|
19
|
+
element[0].style.height = 'calc(100vh - ' + navbarHeight + ')';
|
20
|
+
element[0].style.paddingBottom = '30px';
|
21
|
+
element[0].style.overflow = 'auto';
|
15
22
|
element[0].style.position = 'fixed';
|
16
23
|
}
|
17
24
|
}, 100);
|
@@ -0,0 +1,59 @@
|
|
1
|
+
app.directive('menuItem', function($compile, $stateParams) {
|
2
|
+
|
3
|
+
function checkIfShouldShow(scope, params) {
|
4
|
+
var currentId = params.action ? params.controller + '_action_' + params.action : (params.controller || params.type || params.trait);
|
5
|
+
scope.isActive = scope.link.id === currentId;
|
6
|
+
|
7
|
+
function checkLink(link) {
|
8
|
+
if (link.id === currentId || link.parent === currentId) {
|
9
|
+
return true;
|
10
|
+
} else {
|
11
|
+
if (link.parentRef) {
|
12
|
+
var matches = function(r) { return r.id === currentId; };
|
13
|
+
if (!link.isAction) {
|
14
|
+
if ((link.parentRef.childResources || []).some(matches)) return true;
|
15
|
+
}
|
16
|
+
if ((link.parentRef.actions || []).some(matches)) return true;
|
17
|
+
}
|
18
|
+
|
19
|
+
return (link.childResources || []).some(checkLink) || (link.actions || []).some(checkLink);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
scope.shouldShow = scope.toplevel || checkLink(scope.link);
|
23
|
+
}
|
24
|
+
|
25
|
+
function link(scope) {
|
26
|
+
scope.$on('$stateChangeSuccess', function(e, state, params) {
|
27
|
+
checkIfShouldShow(scope, params);
|
28
|
+
});
|
29
|
+
checkIfShouldShow(scope, $stateParams);
|
30
|
+
}
|
31
|
+
|
32
|
+
return {
|
33
|
+
restrict: 'E',
|
34
|
+
templateUrl: 'views/directives/menu_item.html',
|
35
|
+
scope: {
|
36
|
+
link: '=',
|
37
|
+
toplevel: '='
|
38
|
+
},
|
39
|
+
// hackery to make a recursive directive
|
40
|
+
compile: function(element) {
|
41
|
+
var contents = element.contents().remove();
|
42
|
+
var compiledContents;
|
43
|
+
return {
|
44
|
+
post: function(scope, element){
|
45
|
+
// Compile the contents
|
46
|
+
if(!compiledContents) {
|
47
|
+
compiledContents = $compile(contents);
|
48
|
+
}
|
49
|
+
// Re-add the compiled contents to the element
|
50
|
+
compiledContents(scope, function(clone) {
|
51
|
+
element.append(clone);
|
52
|
+
});
|
53
|
+
|
54
|
+
link(scope, element);
|
55
|
+
}
|
56
|
+
};
|
57
|
+
}
|
58
|
+
};
|
59
|
+
});
|