praxis 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/Gemfile +2 -1
  4. data/README.md +21 -27
  5. data/lib/api_browser/app/index.html +3 -3
  6. data/lib/api_browser/app/js/app.js +23 -3
  7. data/lib/api_browser/app/js/controllers/action.js +33 -21
  8. data/lib/api_browser/app/js/controllers/controller.js +3 -25
  9. data/lib/api_browser/app/js/controllers/menu.js +61 -51
  10. data/lib/api_browser/app/js/controllers/trait.js +10 -0
  11. data/lib/api_browser/app/js/controllers/type.js +8 -5
  12. data/lib/api_browser/app/js/directives/fixed_if_fits.js +9 -2
  13. data/lib/api_browser/app/js/directives/menu_item.js +59 -0
  14. data/lib/api_browser/app/js/directives/readable_list.js +87 -0
  15. data/lib/api_browser/app/js/directives/url.js +16 -0
  16. data/lib/api_browser/app/js/factories/Configuration.js +1 -2
  17. data/lib/api_browser/app/js/factories/Documentation.js +49 -7
  18. data/lib/api_browser/app/js/factories/PageInfo.js +9 -0
  19. data/lib/api_browser/app/js/factories/normalize_attributes.js +1 -2
  20. data/lib/api_browser/app/js/factories/template_for.js +9 -4
  21. data/lib/api_browser/app/sass/modules/_sidebar.scss +54 -15
  22. data/lib/api_browser/app/sass/praxis.scss +4 -0
  23. data/lib/api_browser/app/views/action.html +72 -41
  24. data/lib/api_browser/app/views/builtin/field-selector.html +24 -0
  25. data/lib/api_browser/app/views/controller.html +9 -10
  26. data/lib/api_browser/app/views/directives/menu_item.html +8 -0
  27. data/lib/api_browser/app/views/directives/url.html +3 -0
  28. data/lib/api_browser/app/views/layout.html +2 -2
  29. data/lib/api_browser/app/views/menu.html +8 -14
  30. data/lib/api_browser/app/views/navbar.html +1 -1
  31. data/lib/api_browser/app/views/trait.html +13 -0
  32. data/lib/api_browser/app/views/type/details.html +1 -1
  33. data/lib/api_browser/app/views/type.html +1 -1
  34. data/lib/api_browser/app/views/types/embedded/field-selector.html +13 -0
  35. data/lib/api_browser/app/views/types/label/primitive.html +1 -1
  36. data/lib/api_browser/app/views/types/standalone/array.html +3 -0
  37. data/lib/praxis/action_definition.rb +15 -2
  38. data/lib/praxis/collection.rb +17 -5
  39. data/lib/praxis/controller.rb +12 -3
  40. data/lib/praxis/docs/generator.rb +11 -7
  41. data/lib/praxis/extensions/field_expansion.rb +59 -0
  42. data/lib/praxis/extensions/field_selection/field_selector.rb +125 -0
  43. data/lib/praxis/extensions/field_selection.rb +10 -0
  44. data/lib/praxis/extensions/mapper_selectors.rb +16 -0
  45. data/lib/praxis/extensions/rendering.rb +43 -0
  46. data/lib/praxis/links.rb +1 -0
  47. data/lib/praxis/media_type.rb +87 -3
  48. data/lib/praxis/media_type_collection.rb +1 -1
  49. data/lib/praxis/media_type_identifier.rb +6 -1
  50. data/lib/praxis/plugins/praxis_mapper_plugin.rb +29 -10
  51. data/lib/praxis/restful_doc_generator.rb +11 -8
  52. data/lib/praxis/tasks/api_docs.rb +6 -5
  53. data/lib/praxis/types/multipart_array.rb +1 -1
  54. data/lib/praxis/version.rb +1 -1
  55. data/lib/praxis.rb +5 -0
  56. data/praxis.gemspec +4 -3
  57. data/spec/api_browser/factories/configuration_spec.js +32 -0
  58. data/spec/api_browser/factories/documentation_spec.js +75 -25
  59. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -5
  60. data/spec/praxis/{types/collection_spec.rb → collection_spec.rb} +36 -23
  61. data/spec/praxis/extensions/field_expansion_spec.rb +96 -0
  62. data/spec/praxis/extensions/field_selection/field_selector_spec.rb +92 -0
  63. data/spec/praxis/extensions/rendering_spec.rb +63 -0
  64. data/spec/praxis/links_spec.rb +6 -0
  65. data/spec/praxis/media_type_collection_spec.rb +0 -1
  66. data/spec/praxis/media_type_identifier_spec.rb +15 -1
  67. data/spec/praxis/media_type_spec.rb +101 -3
  68. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +33 -24
  69. data/spec/praxis/request_stages/request_stage_spec.rb +1 -1
  70. data/spec/praxis/types/multipart_array_spec.rb +14 -4
  71. data/spec/spec_app/app/controllers/instances.rb +6 -1
  72. data/spec/spec_app/config/environment.rb +2 -1
  73. data/spec/spec_app/design/resources/instances.rb +1 -0
  74. data/spec/spec_helper.rb +3 -1
  75. data/spec/support/spec_media_types.rb +224 -1
  76. metadata +50 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56c2fbb99665a6c0af6430bb7975fd82f469e58f
4
- data.tar.gz: 8b8c200026e69903049082b28dc43cceaf2e8365
3
+ metadata.gz: 5c3c0d0844cabeb646b294a5a91b3c1fff24c848
4
+ data.tar.gz: edcb41f6707ec368f6a87f0f6273bc6aef558b7b
5
5
  SHA512:
6
- metadata.gz: cd790aa9cb59f989310a1977e2b0584717973ed2ab4090a11643cdeb514227c9c504f6260214839e00ccb472170bf507e9de942129c95ce7f25ea913c71624b0
7
- data.tar.gz: e4746483a28b0e48e39de2ee7b4dec0aaaa06714dc20112a9f306ce0141af1a525e60bc23a09765d5e124a30fd3263bf2c876f4c54f47f5c4482a9d57105c236
6
+ metadata.gz: afecbf0a00ee48c51eb9c49a1e0867431569e9136ef74a1e852800d143f5f672d276fa5ba80c1339629faf23d518d50067693ddb4f05702925fa4fb2071131f1
7
+ data.tar.gz: 6348cd21d00aa34085870a0b8af94a4939b31da4d00c962ab4dd49cb316a699a0412082db7e104c18bfb423a731000f04c86422d228be7ecb2ee156573d0afda
data/CHANGELOG.md CHANGED
@@ -1,7 +1,63 @@
1
- # praxis changelog
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
@@ -1,8 +1,9 @@
1
1
  source 'https://rubygems.org'
2
-
3
2
  gemspec
4
3
 
5
4
  group :test do
6
5
  gem 'nokogiri'
7
6
  gem 'builder'
7
+
8
+ gem 'parslet'
8
9
  end
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 that takes a different approach to creating APIs, an approach that treats both designers and implementors as first class citizens and hold both their hands throughout the API building process. With Praxis you create an API by iterating through the design, review and implementation phases.
10
+ Praxis is a framework for both _designing_ and _implementing_ APIs.
7
11
 
8
- Building APIs with Praxis will result in faster developer times, and result in very precise documentation that is apt for both human (web browsable) or machine consumption (JSON spec files). A very important feature of Praxis is that while the design and implementation phases are done independently, the resulting system will still ensure that they are always consistent and enforced at all times. This enforcement will automatically hold true throughout any number of iterations of the design-review-implementation phases. You can, once
9
- and for all, stop worrying about re-generating server code and keeping it in sync with any change to your code and or docs that you make.
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 generate my-app
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
- ## Philosophy
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 ng-app="DocBrowser">
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
- }).state('root.controller.action', {
26
- url: '/:action'
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
- // Extract the example and attach it to each attribute
8
- _.forEach(['headers', 'params', 'payload'], function(n) {
9
- var set = $scope.action[n];
10
- if (set) {
11
- normalizeAttributes(set, set.type.attributes);
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
- $scope.responses = [];
16
- _.forEach($scope.action.responses, function(response, name) {
17
- response.name = name;
18
- response.options = {
19
- headers: response.headers
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
- if(response.parts_like) {
24
- response.parts_like.isMultipart = true;
25
- response.parts_like.options = {
26
- headers: response.parts_like.headers
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
- $scope.responses.push(response.parts_like);
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, $anchorScroll, $timeout) {
1
+ app.controller('ControllerCtrl', function($scope, $stateParams, Documentation) {
2
2
  $scope.controllerName = $stateParams.controller;
3
3
  $scope.apiVersion = $stateParams.version;
4
4
 
5
- Documentation.getController($stateParams.version, $stateParams.controller).then(function(response) {
6
- $scope.controller = response.data;
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.others = {};
5
+ $scope.schemas = {};
6
+ $scope.traits = {};
6
7
  $scope.selectedVersion = '';
7
8
  $scope.currentType = '';
8
9
  $scope.active = {};
9
10
 
10
- var menuPromise = Documentation.getIndex().success(function(index) {
11
- _.forEach(index, function(items, version) {
12
- $scope.versions.push(version);
13
- var resources = $scope.resources[version] = [];
14
- var others = $scope.others[version] = [];
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
- _.forEach(items, function(item, name) {
17
- var link = { name: name, stateRef: '' };
18
- if (item.parent) {
19
- link.parent = item.parent;
20
- }
21
- if (item.controller) {
22
- parentLookup[item.controller] = link;
23
- link.stateRef = $state.href('root.controller', { version: version, controller: item.controller });
24
- link.typeName = item.controller;
25
- resources.push(link);
26
- } else if (item.media_type) {
27
- link.stateRef = $state.href('root.type', { version: version, type: item.media_type });
28
- link.typeName = item.media_type;
29
- others.push(link);
30
- } else if (item.kind) {
31
- link.stateRef = $state.href('root.type', { version: version, type: item.kind });
32
- link.typeName = item.kind;
33
- others.push(link);
34
- }
35
- });
36
- function getPath(r) {
37
- if (r.parent) {
38
- var path = getPath(parentLookup[r.parent]) + ':' + _.last(r.typeName.split('-'));
39
- return path;
40
- }
41
- return _.last(r.typeName.split('-'));
42
- }
43
- $scope.resources[version] = _.map(_.sortBy(resources, getPath), function(r) {
44
- r.grandfather = grandfatherType(r.typeName);
45
- return r;
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.availableOthers = function() {
63
- return $scope.others[$scope.selectedVersion];
79
+ $scope.availableSchemas = function() {
80
+ return $scope.schemas[$scope.selectedVersion];
64
81
  };
65
82
 
66
- function grandfatherType(id) {
67
- var self = parentLookup[id];
68
- if (self.parent) {
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.currentType = params.controller || params.type;
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.getType($stateParams.version, $scope.typeId).then(function(response) {
8
- $scope.type = response.data;
9
- $scope.views = _(response.data.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.getIndex().success(function(response) {
16
- $scope.controllers = _.select(response[$scope.apiVersion], function(item) { return item.controller && item.media_type == $scope.type.name; });
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
- if (height < $(window).height()) {
14
- element[0].style.width = element.width() + 'px';
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
+ });