dynflow 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. data/README.md +11 -3
  2. data/doc/pages/.gitignore +7 -0
  3. data/doc/pages/Gemfile +8 -0
  4. data/doc/pages/Rakefile +26 -0
  5. data/doc/pages/_config.yml +38 -0
  6. data/doc/pages/plugins/alert_block.rb +27 -0
  7. data/doc/pages/plugins/div_tag.rb +24 -0
  8. data/doc/pages/plugins/graphviz.rb +121 -0
  9. data/doc/pages/plugins/plantuml.rb +85 -0
  10. data/doc/pages/plugins/play.rb +13 -0
  11. data/doc/pages/plugins/tags.rb +138 -0
  12. data/doc/pages/plugins/toc.rb +20 -0
  13. data/doc/pages/source/.nojekyll +0 -0
  14. data/doc/pages/source/404.md +6 -0
  15. data/doc/pages/source/_includes/disqus.html +25 -0
  16. data/doc/pages/source/_includes/google_analytics.html +12 -0
  17. data/doc/pages/source/_includes/google_plus_one.html +2 -0
  18. data/doc/pages/source/_includes/menu.html +19 -0
  19. data/doc/pages/source/_includes/menu_brand.html +2 -0
  20. data/doc/pages/source/_includes/menu_right.html +1 -0
  21. data/doc/pages/source/_includes/post_item.html +10 -0
  22. data/doc/pages/source/_includes/scroll_to.html +24 -0
  23. data/doc/pages/source/_includes/twitter_sharing.html +9 -0
  24. data/doc/pages/source/_layouts/default.html +70 -0
  25. data/doc/pages/source/_layouts/page.html +47 -0
  26. data/doc/pages/source/_layouts/post.html +19 -0
  27. data/doc/pages/source/_layouts/presentation.html +39 -0
  28. data/doc/pages/source/_layouts/tag_page.html +12 -0
  29. data/doc/pages/source/_sass/_bootstrap-compass.scss +9 -0
  30. data/doc/pages/source/_sass/_bootstrap-mincer.scss +19 -0
  31. data/doc/pages/source/_sass/_bootstrap-sprockets.scss +9 -0
  32. data/doc/pages/source/_sass/_bootstrap-variables.sass +865 -0
  33. data/doc/pages/source/_sass/_bootstrap.scss +50 -0
  34. data/doc/pages/source/_sass/_specific.scss +16 -0
  35. data/doc/pages/source/_sass/_style.scss +172 -0
  36. data/doc/pages/source/_sass/bootstrap/_alerts.scss +73 -0
  37. data/doc/pages/source/_sass/bootstrap/_badges.scss +67 -0
  38. data/doc/pages/source/_sass/bootstrap/_breadcrumbs.scss +26 -0
  39. data/doc/pages/source/_sass/bootstrap/_button-groups.scss +243 -0
  40. data/doc/pages/source/_sass/bootstrap/_buttons.scss +160 -0
  41. data/doc/pages/source/_sass/bootstrap/_carousel.scss +269 -0
  42. data/doc/pages/source/_sass/bootstrap/_close.scss +36 -0
  43. data/doc/pages/source/_sass/bootstrap/_code.scss +69 -0
  44. data/doc/pages/source/_sass/bootstrap/_component-animations.scss +38 -0
  45. data/doc/pages/source/_sass/bootstrap/_dropdowns.scss +214 -0
  46. data/doc/pages/source/_sass/bootstrap/_forms.scss +570 -0
  47. data/doc/pages/source/_sass/bootstrap/_glyphicons.scss +301 -0
  48. data/doc/pages/source/_sass/bootstrap/_grid.scss +84 -0
  49. data/doc/pages/source/_sass/bootstrap/_input-groups.scss +166 -0
  50. data/doc/pages/source/_sass/bootstrap/_jumbotron.scss +50 -0
  51. data/doc/pages/source/_sass/bootstrap/_labels.scss +66 -0
  52. data/doc/pages/source/_sass/bootstrap/_list-group.scss +124 -0
  53. data/doc/pages/source/_sass/bootstrap/_media.scss +61 -0
  54. data/doc/pages/source/_sass/bootstrap/_mixins.scss +39 -0
  55. data/doc/pages/source/_sass/bootstrap/_modals.scss +148 -0
  56. data/doc/pages/source/_sass/bootstrap/_navbar.scss +663 -0
  57. data/doc/pages/source/_sass/bootstrap/_navs.scss +244 -0
  58. data/doc/pages/source/_sass/bootstrap/_normalize.scss +427 -0
  59. data/doc/pages/source/_sass/bootstrap/_pager.scss +54 -0
  60. data/doc/pages/source/_sass/bootstrap/_pagination.scss +88 -0
  61. data/doc/pages/source/_sass/bootstrap/_panels.scss +265 -0
  62. data/doc/pages/source/_sass/bootstrap/_popovers.scss +135 -0
  63. data/doc/pages/source/_sass/bootstrap/_print.scss +107 -0
  64. data/doc/pages/source/_sass/bootstrap/_progress-bars.scss +87 -0
  65. data/doc/pages/source/_sass/bootstrap/_responsive-embed.scss +35 -0
  66. data/doc/pages/source/_sass/bootstrap/_responsive-utilities.scss +177 -0
  67. data/doc/pages/source/_sass/bootstrap/_scaffolding.scss +150 -0
  68. data/doc/pages/source/_sass/bootstrap/_tables.scss +234 -0
  69. data/doc/pages/source/_sass/bootstrap/_theme.scss +273 -0
  70. data/doc/pages/source/_sass/bootstrap/_thumbnails.scss +38 -0
  71. data/doc/pages/source/_sass/bootstrap/_tooltip.scss +103 -0
  72. data/doc/pages/source/_sass/bootstrap/_type.scss +298 -0
  73. data/doc/pages/source/_sass/bootstrap/_utilities.scss +56 -0
  74. data/doc/pages/source/_sass/bootstrap/_variables.scss +862 -0
  75. data/doc/pages/source/_sass/bootstrap/_wells.scss +29 -0
  76. data/doc/pages/source/_sass/bootstrap/mixins/_alerts.scss +14 -0
  77. data/doc/pages/source/_sass/bootstrap/mixins/_background-variant.scss +11 -0
  78. data/doc/pages/source/_sass/bootstrap/mixins/_border-radius.scss +18 -0
  79. data/doc/pages/source/_sass/bootstrap/mixins/_buttons.scss +52 -0
  80. data/doc/pages/source/_sass/bootstrap/mixins/_center-block.scss +7 -0
  81. data/doc/pages/source/_sass/bootstrap/mixins/_clearfix.scss +22 -0
  82. data/doc/pages/source/_sass/bootstrap/mixins/_forms.scss +88 -0
  83. data/doc/pages/source/_sass/bootstrap/mixins/_gradients.scss +58 -0
  84. data/doc/pages/source/_sass/bootstrap/mixins/_grid-framework.scss +81 -0
  85. data/doc/pages/source/_sass/bootstrap/mixins/_grid.scss +122 -0
  86. data/doc/pages/source/_sass/bootstrap/mixins/_hide-text.scss +21 -0
  87. data/doc/pages/source/_sass/bootstrap/mixins/_image.scss +33 -0
  88. data/doc/pages/source/_sass/bootstrap/mixins/_labels.scss +12 -0
  89. data/doc/pages/source/_sass/bootstrap/mixins/_list-group.scss +31 -0
  90. data/doc/pages/source/_sass/bootstrap/mixins/_nav-divider.scss +10 -0
  91. data/doc/pages/source/_sass/bootstrap/mixins/_nav-vertical-align.scss +9 -0
  92. data/doc/pages/source/_sass/bootstrap/mixins/_opacity.scss +8 -0
  93. data/doc/pages/source/_sass/bootstrap/mixins/_pagination.scss +23 -0
  94. data/doc/pages/source/_sass/bootstrap/mixins/_panels.scss +24 -0
  95. data/doc/pages/source/_sass/bootstrap/mixins/_progress-bar.scss +10 -0
  96. data/doc/pages/source/_sass/bootstrap/mixins/_reset-filter.scss +8 -0
  97. data/doc/pages/source/_sass/bootstrap/mixins/_resize.scss +6 -0
  98. data/doc/pages/source/_sass/bootstrap/mixins/_responsive-visibility.scss +21 -0
  99. data/doc/pages/source/_sass/bootstrap/mixins/_size.scss +10 -0
  100. data/doc/pages/source/_sass/bootstrap/mixins/_tab-focus.scss +9 -0
  101. data/doc/pages/source/_sass/bootstrap/mixins/_table-row.scss +28 -0
  102. data/doc/pages/source/_sass/bootstrap/mixins/_text-emphasis.scss +11 -0
  103. data/doc/pages/source/_sass/bootstrap/mixins/_text-overflow.scss +8 -0
  104. data/doc/pages/source/_sass/bootstrap/mixins/_vendor-prefixes.scss +222 -0
  105. data/doc/pages/source/atom.xml +32 -0
  106. data/doc/pages/source/bootstrap/config.json +429 -0
  107. data/doc/pages/source/bootstrap/css/bootstrap-theme.css +479 -0
  108. data/doc/pages/source/bootstrap/css/bootstrap-theme.min.css +10 -0
  109. data/doc/pages/source/bootstrap/css/bootstrap.css +6564 -0
  110. data/doc/pages/source/bootstrap/css/bootstrap.min.css +10 -0
  111. data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  112. data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  113. data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  114. data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  115. data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  116. data/doc/pages/source/bootstrap/js/bootstrap.js +2309 -0
  117. data/doc/pages/source/bootstrap/js/bootstrap.min.js +12 -0
  118. data/doc/pages/source/css/app.scss +10 -0
  119. data/doc/pages/source/css/syntax.css +60 -0
  120. data/doc/pages/source/documentation/index.md +977 -0
  121. data/doc/pages/source/faq/index.md +16 -0
  122. data/doc/pages/source/images/dynflow-logos.svg +423 -0
  123. data/doc/pages/source/images/logo-long.png +0 -0
  124. data/doc/pages/source/images/logo-long.svg +116 -0
  125. data/doc/pages/source/images/logo-square.png +0 -0
  126. data/doc/pages/source/images/logo-square.svg +75 -0
  127. data/doc/pages/source/images/noise.png +0 -0
  128. data/doc/pages/source/images/screenshot.png +0 -0
  129. data/doc/pages/source/index.md +64 -0
  130. data/doc/pages/source/media/index.md +20 -0
  131. data/doc/pages/source/projects/index.md +32 -0
  132. data/dynflow.gemspec +2 -3
  133. data/examples/sub_plans.rb +37 -0
  134. data/lib/dynflow/action.rb +28 -8
  135. data/lib/dynflow/action/polling.rb +6 -5
  136. data/lib/dynflow/action/with_sub_plans.rb +162 -0
  137. data/lib/dynflow/execution_plan.rb +25 -7
  138. data/lib/dynflow/execution_plan/steps/abstract.rb +5 -2
  139. data/lib/dynflow/execution_plan/steps/plan_step.rb +6 -2
  140. data/lib/dynflow/execution_plan/steps/run_step.rb +4 -0
  141. data/lib/dynflow/persistence.rb +5 -0
  142. data/lib/dynflow/persistence_adapters/sequel.rb +12 -2
  143. data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +9 -0
  144. data/lib/dynflow/version.rb +1 -1
  145. data/lib/dynflow/web_console.rb +21 -7
  146. data/lib/dynflow/world.rb +26 -2
  147. data/test/action_test.rb +107 -0
  148. data/test/persistance_adapters_test.rb +2 -2
  149. data/test/test_helper.rb +1 -1
  150. data/web/views/flow_step.erb +3 -0
  151. metadata +137 -4
@@ -0,0 +1,12 @@
1
+ /*!
2
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
3
+ * Copyright 2011-2015 Twitter, Inc.
4
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5
+ */
6
+
7
+ /*!
8
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=9d12231ad700ab8f1be0)
9
+ * Config saved to config.json and https://gist.github.com/9d12231ad700ab8f1be0
10
+ */
11
+ if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(t){"use strict";var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),s=i.data("bs.alert");s||i.data("bs.alert",s=new o(this)),"string"==typeof e&&s[e].call(i)})}var i='[data-dismiss="alert"]',o=function(e){t(e).on("click",i,this.close)};o.VERSION="3.3.2",o.TRANSITION_DURATION=150,o.prototype.close=function(e){function i(){a.detach().trigger("closed.bs.alert").remove()}var s=t(this),n=s.attr("data-target");n||(n=s.attr("href"),n=n&&n.replace(/.*(?=#[^\s]*$)/,""));var a=t(n);e&&e.preventDefault(),a.length||(a=s.closest(".alert")),a.trigger(e=t.Event("close.bs.alert")),e.isDefaultPrevented()||(a.removeClass("in"),t.support.transition&&a.hasClass("fade")?a.one("bsTransitionEnd",i).emulateTransitionEnd(o.TRANSITION_DURATION):i())};var s=t.fn.alert;t.fn.alert=e,t.fn.alert.Constructor=o,t.fn.alert.noConflict=function(){return t.fn.alert=s,this},t(document).on("click.bs.alert.data-api",i,o.prototype.close)}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.button"),n="object"==typeof e&&e;s||o.data("bs.button",s=new i(this,n)),"toggle"==e?s.toggle():e&&s.setState(e)})}var i=function(e,o){this.$element=t(e),this.options=t.extend({},i.DEFAULTS,o),this.isLoading=!1};i.VERSION="3.3.2",i.DEFAULTS={loadingText:"loading..."},i.prototype.setState=function(e){var i="disabled",o=this.$element,s=o.is("input")?"val":"html",n=o.data();e+="Text",null==n.resetText&&o.data("resetText",o[s]()),setTimeout(t.proxy(function(){o[s](null==n[e]?this.options[e]:n[e]),"loadingText"==e?(this.isLoading=!0,o.addClass(i).attr(i,i)):this.isLoading&&(this.isLoading=!1,o.removeClass(i).removeAttr(i))},this),0)},i.prototype.toggle=function(){var t=!0,e=this.$element.closest('[data-toggle="buttons"]');if(e.length){var i=this.$element.find("input");"radio"==i.prop("type")&&(i.prop("checked")&&this.$element.hasClass("active")?t=!1:e.find(".active").removeClass("active")),t&&i.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));t&&this.$element.toggleClass("active")};var o=t.fn.button;t.fn.button=e,t.fn.button.Constructor=i,t.fn.button.noConflict=function(){return t.fn.button=o,this},t(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(i){var o=t(i.target);o.hasClass("btn")||(o=o.closest(".btn")),e.call(o,"toggle"),i.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(e){t(e.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(e.type))})}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.carousel"),n=t.extend({},i.DEFAULTS,o.data(),"object"==typeof e&&e),a="string"==typeof e?e:n.slide;s||o.data("bs.carousel",s=new i(this,n)),"number"==typeof e?s.to(e):a?s[a]():n.interval&&s.pause().cycle()})}var i=function(e,i){this.$element=t(e),this.$indicators=this.$element.find(".carousel-indicators"),this.options=i,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",t.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",t.proxy(this.pause,this)).on("mouseleave.bs.carousel",t.proxy(this.cycle,this))};i.VERSION="3.3.2",i.TRANSITION_DURATION=600,i.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},i.prototype.keydown=function(t){if(!/input|textarea/i.test(t.target.tagName)){switch(t.which){case 37:this.prev();break;case 39:this.next();break;default:return}t.preventDefault()}},i.prototype.cycle=function(e){return e||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(t.proxy(this.next,this),this.options.interval)),this},i.prototype.getItemIndex=function(t){return this.$items=t.parent().children(".item"),this.$items.index(t||this.$active)},i.prototype.getItemForDirection=function(t,e){var i=this.getItemIndex(e),o="prev"==t&&0===i||"next"==t&&i==this.$items.length-1;if(o&&!this.options.wrap)return e;var s="prev"==t?-1:1,n=(i+s)%this.$items.length;return this.$items.eq(n)},i.prototype.to=function(t){var e=this,i=this.getItemIndex(this.$active=this.$element.find(".item.active"));return t>this.$items.length-1||0>t?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){e.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",this.$items.eq(t))},i.prototype.pause=function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&t.support.transition&&(this.$element.trigger(t.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},i.prototype.next=function(){return this.sliding?void 0:this.slide("next")},i.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},i.prototype.slide=function(e,o){var s=this.$element.find(".item.active"),n=o||this.getItemForDirection(e,s),a=this.interval,r="next"==e?"left":"right",l=this;if(n.hasClass("active"))return this.sliding=!1;var h=n[0],d=t.Event("slide.bs.carousel",{relatedTarget:h,direction:r});if(this.$element.trigger(d),!d.isDefaultPrevented()){if(this.sliding=!0,a&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var p=t(this.$indicators.children()[this.getItemIndex(n)]);p&&p.addClass("active")}var c=t.Event("slid.bs.carousel",{relatedTarget:h,direction:r});return t.support.transition&&this.$element.hasClass("slide")?(n.addClass(e),n[0].offsetWidth,s.addClass(r),n.addClass(r),s.one("bsTransitionEnd",function(){n.removeClass([e,r].join(" ")).addClass("active"),s.removeClass(["active",r].join(" ")),l.sliding=!1,setTimeout(function(){l.$element.trigger(c)},0)}).emulateTransitionEnd(i.TRANSITION_DURATION)):(s.removeClass("active"),n.addClass("active"),this.sliding=!1,this.$element.trigger(c)),a&&this.cycle(),this}};var o=t.fn.carousel;t.fn.carousel=e,t.fn.carousel.Constructor=i,t.fn.carousel.noConflict=function(){return t.fn.carousel=o,this};var s=function(i){var o,s=t(this),n=t(s.attr("data-target")||(o=s.attr("href"))&&o.replace(/.*(?=#[^\s]+$)/,""));if(n.hasClass("carousel")){var a=t.extend({},n.data(),s.data()),r=s.attr("data-slide-to");r&&(a.interval=!1),e.call(n,a),r&&n.data("bs.carousel").to(r),i.preventDefault()}};t(document).on("click.bs.carousel.data-api","[data-slide]",s).on("click.bs.carousel.data-api","[data-slide-to]",s),t(window).on("load",function(){t('[data-ride="carousel"]').each(function(){var i=t(this);e.call(i,i.data())})})}(jQuery),+function(t){"use strict";function e(e){e&&3===e.which||(t(s).remove(),t(n).each(function(){var o=t(this),s=i(o),n={relatedTarget:this};s.hasClass("open")&&(s.trigger(e=t.Event("hide.bs.dropdown",n)),e.isDefaultPrevented()||(o.attr("aria-expanded","false"),s.removeClass("open").trigger("hidden.bs.dropdown",n)))}))}function i(e){var i=e.attr("data-target");i||(i=e.attr("href"),i=i&&/#[A-Za-z]/.test(i)&&i.replace(/.*(?=#[^\s]*$)/,""));var o=i&&t(i);return o&&o.length?o:e.parent()}function o(e){return this.each(function(){var i=t(this),o=i.data("bs.dropdown");o||i.data("bs.dropdown",o=new a(this)),"string"==typeof e&&o[e].call(i)})}var s=".dropdown-backdrop",n='[data-toggle="dropdown"]',a=function(e){t(e).on("click.bs.dropdown",this.toggle)};a.VERSION="3.3.2",a.prototype.toggle=function(o){var s=t(this);if(!s.is(".disabled, :disabled")){var n=i(s),a=n.hasClass("open");if(e(),!a){"ontouchstart"in document.documentElement&&!n.closest(".navbar-nav").length&&t('<div class="dropdown-backdrop"/>').insertAfter(t(this)).on("click",e);var r={relatedTarget:this};if(n.trigger(o=t.Event("show.bs.dropdown",r)),o.isDefaultPrevented())return;s.trigger("focus").attr("aria-expanded","true"),n.toggleClass("open").trigger("shown.bs.dropdown",r)}return!1}},a.prototype.keydown=function(e){if(/(38|40|27|32)/.test(e.which)&&!/input|textarea/i.test(e.target.tagName)){var o=t(this);if(e.preventDefault(),e.stopPropagation(),!o.is(".disabled, :disabled")){var s=i(o),a=s.hasClass("open");if(!a&&27!=e.which||a&&27==e.which)return 27==e.which&&s.find(n).trigger("focus"),o.trigger("click");var r=" li:not(.divider):visible a",l=s.find('[role="menu"]'+r+', [role="listbox"]'+r);if(l.length){var h=l.index(e.target);38==e.which&&h>0&&h--,40==e.which&&h<l.length-1&&h++,~h||(h=0),l.eq(h).trigger("focus")}}}};var r=t.fn.dropdown;t.fn.dropdown=o,t.fn.dropdown.Constructor=a,t.fn.dropdown.noConflict=function(){return t.fn.dropdown=r,this},t(document).on("click.bs.dropdown.data-api",e).on("click.bs.dropdown.data-api",".dropdown form",function(t){t.stopPropagation()}).on("click.bs.dropdown.data-api",n,a.prototype.toggle).on("keydown.bs.dropdown.data-api",n,a.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',a.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',a.prototype.keydown)}(jQuery),+function(t){"use strict";function e(e,o){return this.each(function(){var s=t(this),n=s.data("bs.modal"),a=t.extend({},i.DEFAULTS,s.data(),"object"==typeof e&&e);n||s.data("bs.modal",n=new i(this,a)),"string"==typeof e?n[e](o):a.show&&n.show(o)})}var i=function(e,i){this.options=i,this.$body=t(document.body),this.$element=t(e),this.$backdrop=this.isShown=null,this.scrollbarWidth=0,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,t.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};i.VERSION="3.3.2",i.TRANSITION_DURATION=300,i.BACKDROP_TRANSITION_DURATION=150,i.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},i.prototype.toggle=function(t){return this.isShown?this.hide():this.show(t)},i.prototype.show=function(e){var o=this,s=t.Event("show.bs.modal",{relatedTarget:e});this.$element.trigger(s),this.isShown||s.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',t.proxy(this.hide,this)),this.backdrop(function(){var s=t.support.transition&&o.$element.hasClass("fade");o.$element.parent().length||o.$element.appendTo(o.$body),o.$element.show().scrollTop(0),o.options.backdrop&&o.adjustBackdrop(),o.adjustDialog(),s&&o.$element[0].offsetWidth,o.$element.addClass("in").attr("aria-hidden",!1),o.enforceFocus();var n=t.Event("shown.bs.modal",{relatedTarget:e});s?o.$element.find(".modal-dialog").one("bsTransitionEnd",function(){o.$element.trigger("focus").trigger(n)}).emulateTransitionEnd(i.TRANSITION_DURATION):o.$element.trigger("focus").trigger(n)}))},i.prototype.hide=function(e){e&&e.preventDefault(),e=t.Event("hide.bs.modal"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),t(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),t.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",t.proxy(this.hideModal,this)).emulateTransitionEnd(i.TRANSITION_DURATION):this.hideModal())},i.prototype.enforceFocus=function(){t(document).off("focusin.bs.modal").on("focusin.bs.modal",t.proxy(function(t){this.$element[0]===t.target||this.$element.has(t.target).length||this.$element.trigger("focus")},this))},i.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",t.proxy(function(t){27==t.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},i.prototype.resize=function(){this.isShown?t(window).on("resize.bs.modal",t.proxy(this.handleUpdate,this)):t(window).off("resize.bs.modal")},i.prototype.hideModal=function(){var t=this;this.$element.hide(),this.backdrop(function(){t.$body.removeClass("modal-open"),t.resetAdjustments(),t.resetScrollbar(),t.$element.trigger("hidden.bs.modal")})},i.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},i.prototype.backdrop=function(e){var o=this,s=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var n=t.support.transition&&s;if(this.$backdrop=t('<div class="modal-backdrop '+s+'" />').prependTo(this.$element).on("click.dismiss.bs.modal",t.proxy(function(t){t.target===t.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),n&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!e)return;n?this.$backdrop.one("bsTransitionEnd",e).emulateTransitionEnd(i.BACKDROP_TRANSITION_DURATION):e()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var a=function(){o.removeBackdrop(),e&&e()};t.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",a).emulateTransitionEnd(i.BACKDROP_TRANSITION_DURATION):a()}else e&&e()},i.prototype.handleUpdate=function(){this.options.backdrop&&this.adjustBackdrop(),this.adjustDialog()},i.prototype.adjustBackdrop=function(){this.$backdrop.css("height",0).css("height",this.$element[0].scrollHeight)},i.prototype.adjustDialog=function(){var t=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&t?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!t?this.scrollbarWidth:""})},i.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},i.prototype.checkScrollbar=function(){this.bodyIsOverflowing=document.body.scrollHeight>document.documentElement.clientHeight,this.scrollbarWidth=this.measureScrollbar()},i.prototype.setScrollbar=function(){var t=parseInt(this.$body.css("padding-right")||0,10);this.bodyIsOverflowing&&this.$body.css("padding-right",t+this.scrollbarWidth)},i.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},i.prototype.measureScrollbar=function(){var t=document.createElement("div");t.className="modal-scrollbar-measure",this.$body.append(t);var e=t.offsetWidth-t.clientWidth;return this.$body[0].removeChild(t),e};var o=t.fn.modal;t.fn.modal=e,t.fn.modal.Constructor=i,t.fn.modal.noConflict=function(){return t.fn.modal=o,this},t(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(i){var o=t(this),s=o.attr("href"),n=t(o.attr("data-target")||s&&s.replace(/.*(?=#[^\s]+$)/,"")),a=n.data("bs.modal")?"toggle":t.extend({remote:!/#/.test(s)&&s},n.data(),o.data());o.is("a")&&i.preventDefault(),n.one("show.bs.modal",function(t){t.isDefaultPrevented()||n.one("hidden.bs.modal",function(){o.is(":visible")&&o.trigger("focus")})}),e.call(n,a,this)})}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.tooltip"),n="object"==typeof e&&e;(s||"destroy"!=e)&&(s||o.data("bs.tooltip",s=new i(this,n)),"string"==typeof e&&s[e]())})}var i=function(t,e){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",t,e)};i.VERSION="3.3.2",i.TRANSITION_DURATION=150,i.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},i.prototype.init=function(e,i,o){this.enabled=!0,this.type=e,this.$element=t(i),this.options=this.getOptions(o),this.$viewport=this.options.viewport&&t(this.options.viewport.selector||this.options.viewport);for(var s=this.options.trigger.split(" "),n=s.length;n--;){var a=s[n];if("click"==a)this.$element.on("click."+this.type,this.options.selector,t.proxy(this.toggle,this));else if("manual"!=a){var r="hover"==a?"mouseenter":"focusin",l="hover"==a?"mouseleave":"focusout";this.$element.on(r+"."+this.type,this.options.selector,t.proxy(this.enter,this)),this.$element.on(l+"."+this.type,this.options.selector,t.proxy(this.leave,this))}}this.options.selector?this._options=t.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},i.prototype.getDefaults=function(){return i.DEFAULTS},i.prototype.getOptions=function(e){return e=t.extend({},this.getDefaults(),this.$element.data(),e),e.delay&&"number"==typeof e.delay&&(e.delay={show:e.delay,hide:e.delay}),e},i.prototype.getDelegateOptions=function(){var e={},i=this.getDefaults();return this._options&&t.each(this._options,function(t,o){i[t]!=o&&(e[t]=o)}),e},i.prototype.enter=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return i&&i.$tip&&i.$tip.is(":visible")?void(i.hoverState="in"):(i||(i=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,i)),clearTimeout(i.timeout),i.hoverState="in",i.options.delay&&i.options.delay.show?void(i.timeout=setTimeout(function(){"in"==i.hoverState&&i.show()},i.options.delay.show)):i.show())},i.prototype.leave=function(e){var i=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return i||(i=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,i)),clearTimeout(i.timeout),i.hoverState="out",i.options.delay&&i.options.delay.hide?void(i.timeout=setTimeout(function(){"out"==i.hoverState&&i.hide()},i.options.delay.hide)):i.hide()},i.prototype.show=function(){var e=t.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(e);var o=t.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(e.isDefaultPrevented()||!o)return;var s=this,n=this.tip(),a=this.getUID(this.type);this.setContent(),n.attr("id",a),this.$element.attr("aria-describedby",a),this.options.animation&&n.addClass("fade");var r="function"==typeof this.options.placement?this.options.placement.call(this,n[0],this.$element[0]):this.options.placement,l=/\s?auto?\s?/i,h=l.test(r);h&&(r=r.replace(l,"")||"top"),n.detach().css({top:0,left:0,display:"block"}).addClass(r).data("bs."+this.type,this),this.options.container?n.appendTo(this.options.container):n.insertAfter(this.$element);var d=this.getPosition(),p=n[0].offsetWidth,c=n[0].offsetHeight;if(h){var f=r,u=this.options.container?t(this.options.container):this.$element.parent(),g=this.getPosition(u);r="bottom"==r&&d.bottom+c>g.bottom?"top":"top"==r&&d.top-c<g.top?"bottom":"right"==r&&d.right+p>g.width?"left":"left"==r&&d.left-p<g.left?"right":r,n.removeClass(f).addClass(r)}var v=this.getCalculatedOffset(r,d,p,c);this.applyPlacement(v,r);var m=function(){var t=s.hoverState;s.$element.trigger("shown.bs."+s.type),s.hoverState=null,"out"==t&&s.leave(s)};t.support.transition&&this.$tip.hasClass("fade")?n.one("bsTransitionEnd",m).emulateTransitionEnd(i.TRANSITION_DURATION):m()}},i.prototype.applyPlacement=function(e,i){var o=this.tip(),s=o[0].offsetWidth,n=o[0].offsetHeight,a=parseInt(o.css("margin-top"),10),r=parseInt(o.css("margin-left"),10);isNaN(a)&&(a=0),isNaN(r)&&(r=0),e.top=e.top+a,e.left=e.left+r,t.offset.setOffset(o[0],t.extend({using:function(t){o.css({top:Math.round(t.top),left:Math.round(t.left)})}},e),0),o.addClass("in");var l=o[0].offsetWidth,h=o[0].offsetHeight;"top"==i&&h!=n&&(e.top=e.top+n-h);var d=this.getViewportAdjustedDelta(i,e,l,h);d.left?e.left+=d.left:e.top+=d.top;var p=/top|bottom/.test(i),c=p?2*d.left-s+l:2*d.top-n+h,f=p?"offsetWidth":"offsetHeight";o.offset(e),this.replaceArrow(c,o[0][f],p)},i.prototype.replaceArrow=function(t,e,i){this.arrow().css(i?"left":"top",50*(1-t/e)+"%").css(i?"top":"left","")},i.prototype.setContent=function(){var t=this.tip(),e=this.getTitle();t.find(".tooltip-inner")[this.options.html?"html":"text"](e),t.removeClass("fade in top bottom left right")},i.prototype.hide=function(e){function o(){"in"!=s.hoverState&&n.detach(),s.$element.removeAttr("aria-describedby").trigger("hidden.bs."+s.type),e&&e()}var s=this,n=this.tip(),a=t.Event("hide.bs."+this.type);return this.$element.trigger(a),a.isDefaultPrevented()?void 0:(n.removeClass("in"),t.support.transition&&this.$tip.hasClass("fade")?n.one("bsTransitionEnd",o).emulateTransitionEnd(i.TRANSITION_DURATION):o(),this.hoverState=null,this)},i.prototype.fixTitle=function(){var t=this.$element;(t.attr("title")||"string"!=typeof t.attr("data-original-title"))&&t.attr("data-original-title",t.attr("title")||"").attr("title","")},i.prototype.hasContent=function(){return this.getTitle()},i.prototype.getPosition=function(e){e=e||this.$element;var i=e[0],o="BODY"==i.tagName,s=i.getBoundingClientRect();null==s.width&&(s=t.extend({},s,{width:s.right-s.left,height:s.bottom-s.top}));var n=o?{top:0,left:0}:e.offset(),a={scroll:o?document.documentElement.scrollTop||document.body.scrollTop:e.scrollTop()},r=o?{width:t(window).width(),height:t(window).height()}:null;return t.extend({},s,a,r,n)},i.prototype.getCalculatedOffset=function(t,e,i,o){return"bottom"==t?{top:e.top+e.height,left:e.left+e.width/2-i/2}:"top"==t?{top:e.top-o,left:e.left+e.width/2-i/2}:"left"==t?{top:e.top+e.height/2-o/2,left:e.left-i}:{top:e.top+e.height/2-o/2,left:e.left+e.width}},i.prototype.getViewportAdjustedDelta=function(t,e,i,o){var s={top:0,left:0};if(!this.$viewport)return s;var n=this.options.viewport&&this.options.viewport.padding||0,a=this.getPosition(this.$viewport);if(/right|left/.test(t)){var r=e.top-n-a.scroll,l=e.top+n-a.scroll+o;r<a.top?s.top=a.top-r:l>a.top+a.height&&(s.top=a.top+a.height-l)}else{var h=e.left-n,d=e.left+n+i;h<a.left?s.left=a.left-h:d>a.width&&(s.left=a.left+a.width-d)}return s},i.prototype.getTitle=function(){var t,e=this.$element,i=this.options;return t=e.attr("data-original-title")||("function"==typeof i.title?i.title.call(e[0]):i.title)},i.prototype.getUID=function(t){do t+=~~(1e6*Math.random());while(document.getElementById(t));return t},i.prototype.tip=function(){return this.$tip=this.$tip||t(this.options.template)},i.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},i.prototype.enable=function(){this.enabled=!0},i.prototype.disable=function(){this.enabled=!1},i.prototype.toggleEnabled=function(){this.enabled=!this.enabled},i.prototype.toggle=function(e){var i=this;e&&(i=t(e.currentTarget).data("bs."+this.type),i||(i=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,i))),i.tip().hasClass("in")?i.leave(i):i.enter(i)},i.prototype.destroy=function(){var t=this;clearTimeout(this.timeout),this.hide(function(){t.$element.off("."+t.type).removeData("bs."+t.type)})};var o=t.fn.tooltip;t.fn.tooltip=e,t.fn.tooltip.Constructor=i,t.fn.tooltip.noConflict=function(){return t.fn.tooltip=o,this}}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.popover"),n="object"==typeof e&&e;(s||"destroy"!=e)&&(s||o.data("bs.popover",s=new i(this,n)),"string"==typeof e&&s[e]())})}var i=function(t,e){this.init("popover",t,e)};if(!t.fn.tooltip)throw new Error("Popover requires tooltip.js");i.VERSION="3.3.2",i.DEFAULTS=t.extend({},t.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),i.prototype=t.extend({},t.fn.tooltip.Constructor.prototype),i.prototype.constructor=i,i.prototype.getDefaults=function(){return i.DEFAULTS},i.prototype.setContent=function(){var t=this.tip(),e=this.getTitle(),i=this.getContent();t.find(".popover-title")[this.options.html?"html":"text"](e),t.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof i?"html":"append":"text"](i),t.removeClass("fade top bottom left right in"),t.find(".popover-title").html()||t.find(".popover-title").hide()},i.prototype.hasContent=function(){return this.getTitle()||this.getContent()},i.prototype.getContent=function(){var t=this.$element,e=this.options;return t.attr("data-content")||("function"==typeof e.content?e.content.call(t[0]):e.content)},i.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},i.prototype.tip=function(){return this.$tip||(this.$tip=t(this.options.template)),this.$tip};var o=t.fn.popover;t.fn.popover=e,t.fn.popover.Constructor=i,t.fn.popover.noConflict=function(){return t.fn.popover=o,this}}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.tab");s||o.data("bs.tab",s=new i(this)),"string"==typeof e&&s[e]()})}var i=function(e){this.element=t(e)};i.VERSION="3.3.2",i.TRANSITION_DURATION=150,i.prototype.show=function(){var e=this.element,i=e.closest("ul:not(.dropdown-menu)"),o=e.data("target");if(o||(o=e.attr("href"),o=o&&o.replace(/.*(?=#[^\s]*$)/,"")),!e.parent("li").hasClass("active")){var s=i.find(".active:last a"),n=t.Event("hide.bs.tab",{relatedTarget:e[0]}),a=t.Event("show.bs.tab",{relatedTarget:s[0]});if(s.trigger(n),e.trigger(a),!a.isDefaultPrevented()&&!n.isDefaultPrevented()){var r=t(o);this.activate(e.closest("li"),i),this.activate(r,r.parent(),function(){s.trigger({type:"hidden.bs.tab",relatedTarget:e[0]}),e.trigger({type:"shown.bs.tab",relatedTarget:s[0]})})}}},i.prototype.activate=function(e,o,s){function n(){a.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),e.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),r?(e[0].offsetWidth,e.addClass("in")):e.removeClass("fade"),e.parent(".dropdown-menu")&&e.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),s&&s()}var a=o.find("> .active"),r=s&&t.support.transition&&(a.length&&a.hasClass("fade")||!!o.find("> .fade").length);a.length&&r?a.one("bsTransitionEnd",n).emulateTransitionEnd(i.TRANSITION_DURATION):n(),a.removeClass("in")};var o=t.fn.tab;t.fn.tab=e,t.fn.tab.Constructor=i,t.fn.tab.noConflict=function(){return t.fn.tab=o,this};var s=function(i){i.preventDefault(),e.call(t(this),"show")};t(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',s).on("click.bs.tab.data-api",'[data-toggle="pill"]',s)}(jQuery),+function(t){"use strict";function e(e){return this.each(function(){var o=t(this),s=o.data("bs.affix"),n="object"==typeof e&&e;s||o.data("bs.affix",s=new i(this,n)),"string"==typeof e&&s[e]()})}var i=function(e,o){this.options=t.extend({},i.DEFAULTS,o),this.$target=t(this.options.target).on("scroll.bs.affix.data-api",t.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",t.proxy(this.checkPositionWithEventLoop,this)),this.$element=t(e),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};i.VERSION="3.3.2",i.RESET="affix affix-top affix-bottom",i.DEFAULTS={offset:0,target:window},i.prototype.getState=function(t,e,i,o){var s=this.$target.scrollTop(),n=this.$element.offset(),a=this.$target.height();if(null!=i&&"top"==this.affixed)return i>s?"top":!1;if("bottom"==this.affixed)return null!=i?s+this.unpin<=n.top?!1:"bottom":t-o>=s+a?!1:"bottom";var r=null==this.affixed,l=r?s:n.top,h=r?a:e;return null!=i&&i>=s?"top":null!=o&&l+h>=t-o?"bottom":!1},i.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(i.RESET).addClass("affix");var t=this.$target.scrollTop(),e=this.$element.offset();return this.pinnedOffset=e.top-t},i.prototype.checkPositionWithEventLoop=function(){setTimeout(t.proxy(this.checkPosition,this),1)},i.prototype.checkPosition=function(){if(this.$element.is(":visible")){var e=this.$element.height(),o=this.options.offset,s=o.top,n=o.bottom,a=t("body").height();"object"!=typeof o&&(n=s=o),"function"==typeof s&&(s=o.top(this.$element)),"function"==typeof n&&(n=o.bottom(this.$element));var r=this.getState(a,e,s,n);if(this.affixed!=r){null!=this.unpin&&this.$element.css("top","");var l="affix"+(r?"-"+r:""),h=t.Event(l+".bs.affix");if(this.$element.trigger(h),h.isDefaultPrevented())return;this.affixed=r,this.unpin="bottom"==r?this.getPinnedOffset():null,this.$element.removeClass(i.RESET).addClass(l).trigger(l.replace("affix","affixed")+".bs.affix")}"bottom"==r&&this.$element.offset({top:a-e-n})}};var o=t.fn.affix;t.fn.affix=e,t.fn.affix.Constructor=i,t.fn.affix.noConflict=function(){return t.fn.affix=o,this},t(window).on("load",function(){t('[data-spy="affix"]').each(function(){var i=t(this),o=i.data();o.offset=o.offset||{},null!=o.offsetBottom&&(o.offset.bottom=o.offsetBottom),null!=o.offsetTop&&(o.offset.top=o.offsetTop),e.call(i,o)})})}(jQuery),+function(t){"use strict";function e(e){var i,o=e.attr("data-target")||(i=e.attr("href"))&&i.replace(/.*(?=#[^\s]+$)/,"");return t(o)}function i(e){return this.each(function(){var i=t(this),s=i.data("bs.collapse"),n=t.extend({},o.DEFAULTS,i.data(),"object"==typeof e&&e);!s&&n.toggle&&"show"==e&&(n.toggle=!1),s||i.data("bs.collapse",s=new o(this,n)),"string"==typeof e&&s[e]()})}var o=function(e,i){this.$element=t(e),this.options=t.extend({},o.DEFAULTS,i),this.$trigger=t(this.options.trigger).filter('[href="#'+e.id+'"], [data-target="#'+e.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};o.VERSION="3.3.2",o.TRANSITION_DURATION=350,o.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},o.prototype.dimension=function(){var t=this.$element.hasClass("width");return t?"width":"height"},o.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var e,s=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(s&&s.length&&(e=s.data("bs.collapse"),e&&e.transitioning))){var n=t.Event("show.bs.collapse");if(this.$element.trigger(n),!n.isDefaultPrevented()){s&&s.length&&(i.call(s,"hide"),e||s.data("bs.collapse",null));var a=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[a](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var r=function(){this.$element.removeClass("collapsing").addClass("collapse in")[a](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!t.support.transition)return r.call(this);var l=t.camelCase(["scroll",a].join("-"));this.$element.one("bsTransitionEnd",t.proxy(r,this)).emulateTransitionEnd(o.TRANSITION_DURATION)[a](this.$element[0][l])}}}},o.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var e=t.Event("hide.bs.collapse");if(this.$element.trigger(e),!e.isDefaultPrevented()){var i=this.dimension();this.$element[i](this.$element[i]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var s=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return t.support.transition?void this.$element[i](0).one("bsTransitionEnd",t.proxy(s,this)).emulateTransitionEnd(o.TRANSITION_DURATION):s.call(this)}}},o.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},o.prototype.getParent=function(){return t(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(t.proxy(function(i,o){var s=t(o);this.addAriaAndCollapsedClass(e(s),s)},this)).end()},o.prototype.addAriaAndCollapsedClass=function(t,e){var i=t.hasClass("in");t.attr("aria-expanded",i),e.toggleClass("collapsed",!i).attr("aria-expanded",i)};var s=t.fn.collapse;t.fn.collapse=i,t.fn.collapse.Constructor=o,t.fn.collapse.noConflict=function(){return t.fn.collapse=s,this},t(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(o){var s=t(this);s.attr("data-target")||o.preventDefault();
12
+ var n=e(s),a=n.data("bs.collapse"),r=a?"toggle":t.extend({},s.data(),{trigger:this});i.call(n,r)})}(jQuery),+function(t){"use strict";function e(i,o){var s=t.proxy(this.process,this);this.$body=t("body"),this.$scrollElement=t(t(i).is("body")?window:i),this.options=t.extend({},e.DEFAULTS,o),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",s),this.refresh(),this.process()}function i(i){return this.each(function(){var o=t(this),s=o.data("bs.scrollspy"),n="object"==typeof i&&i;s||o.data("bs.scrollspy",s=new e(this,n)),"string"==typeof i&&s[i]()})}e.VERSION="3.3.2",e.DEFAULTS={offset:10},e.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},e.prototype.refresh=function(){var e="offset",i=0;t.isWindow(this.$scrollElement[0])||(e="position",i=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var o=this;this.$body.find(this.selector).map(function(){var o=t(this),s=o.data("target")||o.attr("href"),n=/^#./.test(s)&&t(s);return n&&n.length&&n.is(":visible")&&[[n[e]().top+i,s]]||null}).sort(function(t,e){return t[0]-e[0]}).each(function(){o.offsets.push(this[0]),o.targets.push(this[1])})},e.prototype.process=function(){var t,e=this.$scrollElement.scrollTop()+this.options.offset,i=this.getScrollHeight(),o=this.options.offset+i-this.$scrollElement.height(),s=this.offsets,n=this.targets,a=this.activeTarget;if(this.scrollHeight!=i&&this.refresh(),e>=o)return a!=(t=n[n.length-1])&&this.activate(t);if(a&&e<s[0])return this.activeTarget=null,this.clear();for(t=s.length;t--;)a!=n[t]&&e>=s[t]&&(!s[t+1]||e<=s[t+1])&&this.activate(n[t])},e.prototype.activate=function(e){this.activeTarget=e,this.clear();var i=this.selector+'[data-target="'+e+'"],'+this.selector+'[href="'+e+'"]',o=t(i).parents("li").addClass("active");o.parent(".dropdown-menu").length&&(o=o.closest("li.dropdown").addClass("active")),o.trigger("activate.bs.scrollspy")},e.prototype.clear=function(){t(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var o=t.fn.scrollspy;t.fn.scrollspy=i,t.fn.scrollspy.Constructor=e,t.fn.scrollspy.noConflict=function(){return t.fn.scrollspy=o,this},t(window).on("load.bs.scrollspy.data-api",function(){t('[data-spy="scroll"]').each(function(){var e=t(this);i.call(e,e.data())})})}(jQuery),+function(t){"use strict";function e(){var t=document.createElement("bootstrap"),e={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var i in e)if(void 0!==t.style[i])return{end:e[i]};return!1}t.fn.emulateTransitionEnd=function(e){var i=!1,o=this;t(this).one("bsTransitionEnd",function(){i=!0});var s=function(){i||t(o).trigger(t.support.transition.end)};return setTimeout(s,e),this},t(function(){t.support.transition=e(),t.support.transition&&(t.event.special.bsTransitionEnd={bindType:t.support.transition.end,delegateType:t.support.transition.end,handle:function(e){return t(e.target).is(this)?e.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery);
@@ -0,0 +1,10 @@
1
+ ---
2
+ # Front matter comment to ensure Jekyll properly reads file.
3
+ ---
4
+
5
+ @import "../_sass/bootstrap/variables";
6
+ @import "../_sass/bootstrap-variables";
7
+ @import "../_sass/bootstrap";
8
+ @import "../_sass/style";
9
+ @import "../_sass/specific";
10
+
@@ -0,0 +1,60 @@
1
+ .highlight { background: #ffffff; }
2
+ .highlight .c { color: #999988; font-style: italic } /* Comment */
3
+ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
4
+ .highlight .k { font-weight: bold } /* Keyword */
5
+ .highlight .o { font-weight: bold } /* Operator */
6
+ .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
7
+ .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
8
+ .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
9
+ .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
10
+ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
11
+ .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
12
+ .highlight .ge { font-style: italic } /* Generic.Emph */
13
+ .highlight .gr { color: #aa0000 } /* Generic.Error */
14
+ .highlight .gh { color: #999999 } /* Generic.Heading */
15
+ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
16
+ .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
17
+ .highlight .go { color: #888888 } /* Generic.Output */
18
+ .highlight .gp { color: #555555 } /* Generic.Prompt */
19
+ .highlight .gs { font-weight: bold } /* Generic.Strong */
20
+ .highlight .gu { color: #aaaaaa } /* Generic.Subheading */
21
+ .highlight .gt { color: #aa0000 } /* Generic.Traceback */
22
+ .highlight .kc { font-weight: bold } /* Keyword.Constant */
23
+ .highlight .kd { font-weight: bold } /* Keyword.Declaration */
24
+ .highlight .kp { font-weight: bold } /* Keyword.Pseudo */
25
+ .highlight .kr { font-weight: bold } /* Keyword.Reserved */
26
+ .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
27
+ .highlight .m { color: #009999 } /* Literal.Number */
28
+ .highlight .s { color: #d14 } /* Literal.String */
29
+ .highlight .na { color: #008080 } /* Name.Attribute */
30
+ .highlight .nb { color: #0086B3 } /* Name.Builtin */
31
+ .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
32
+ .highlight .no { color: #008080 } /* Name.Constant */
33
+ .highlight .ni { color: #800080 } /* Name.Entity */
34
+ .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
35
+ .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
36
+ .highlight .nn { color: #555555 } /* Name.Namespace */
37
+ .highlight .nt { color: #000080 } /* Name.Tag */
38
+ .highlight .nv { color: #008080 } /* Name.Variable */
39
+ .highlight .ow { font-weight: bold } /* Operator.Word */
40
+ .highlight .w { color: #bbbbbb } /* Text.Whitespace */
41
+ .highlight .mf { color: #009999 } /* Literal.Number.Float */
42
+ .highlight .mh { color: #009999 } /* Literal.Number.Hex */
43
+ .highlight .mi { color: #009999 } /* Literal.Number.Integer */
44
+ .highlight .mo { color: #009999 } /* Literal.Number.Oct */
45
+ .highlight .sb { color: #d14 } /* Literal.String.Backtick */
46
+ .highlight .sc { color: #d14 } /* Literal.String.Char */
47
+ .highlight .sd { color: #d14 } /* Literal.String.Doc */
48
+ .highlight .s2 { color: #d14 } /* Literal.String.Double */
49
+ .highlight .se { color: #d14 } /* Literal.String.Escape */
50
+ .highlight .sh { color: #d14 } /* Literal.String.Heredoc */
51
+ .highlight .si { color: #d14 } /* Literal.String.Interpol */
52
+ .highlight .sx { color: #d14 } /* Literal.String.Other */
53
+ .highlight .sr { color: #009926 } /* Literal.String.Regex */
54
+ .highlight .s1 { color: #d14 } /* Literal.String.Single */
55
+ .highlight .ss { color: #990073 } /* Literal.String.Symbol */
56
+ .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
57
+ .highlight .vc { color: #008080 } /* Name.Variable.Class */
58
+ .highlight .vg { color: #008080 } /* Name.Variable.Global */
59
+ .highlight .vi { color: #008080 } /* Name.Variable.Instance */
60
+ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
@@ -0,0 +1,977 @@
1
+ ---
2
+ layout: page
3
+ title: Documentation
4
+ countheads: true
5
+ toc: true
6
+ comments: true
7
+ ---
8
+
9
+ {% danger_block %}
10
+
11
+ Work in progress! It contains a lot of tpyos, please let us know. There are comments at the bottom
12
+ or you can submit a PR against [pages branch](https://github.com/dynflow/dynflow/tree/pages).
13
+
14
+ Please help with the documentation if you know Dynflow.
15
+
16
+ Thanks!
17
+
18
+ {% enddanger_block %}
19
+
20
+ ## High level overview TODO
21
+
22
+ *TODO to be refined*
23
+
24
+ Dynflow (**DYN**amic work**FLOW**) is a workflow engine
25
+ written in Ruby that allows to:
26
+
27
+ - Keep track of the progress of running processes
28
+ - Run the code asynchronously
29
+ - Resume the process when something goes wrong, skip some steps when needed
30
+ - Detect independent parts and run them concurrently
31
+ - Compose simple actions into more complex scenarios
32
+ - Extend the workflows from third-party libraries
33
+ - Keep consistency between local transactional database and
34
+ external services
35
+ - Suspend the long-running steps, not blocking the thread pool
36
+ - Cancel steps when possible
37
+ - Extend the actions behavior with middlewares
38
+ - Pick different adapters to provide: storage backend, transactions, or executor implementation
39
+
40
+ Dynflow has been developed to be able to support orchestration of services in the
41
+ [Katello](http://katello.org) and [Foreman](http://theforeman.org/) projects.
42
+
43
+ *TODO*
44
+
45
+ - what problems does Dynflow solve?
46
+ - maybe a little history
47
+
48
+ ## Glossary
49
+
50
+ - **Action** - building block of execution plans, a Ruby class inherited
51
+ from `Dynflow::Action`, defines code to be run in each phase.
52
+ - **Phase** - Each action has three phases: `plan`, `run`, `finalize`.
53
+ - **Input** - A `Hash` of data coming to the action.
54
+ - **Output** - A `Hash` of data that the action produces. It's
55
+ persisted and can be used as input of other actions.
56
+ - **Execution plan** - definition of the workflow: product of the plan phase,
57
+ - **Triggering an action** - entering the plan phase, starting with the plan
58
+ method of the action. The execution follows immediately.
59
+ - **Flow** - definition of the `run`/`finalize` phase, holding the information
60
+ about steps that can run concurrently/in sequence. Part of execution plan.
61
+ - **Executor** - service that executes the run and finalize flows based on
62
+ the execution plan. It can run in the same process as the plan phase or in
63
+ different process (using the remote executor).
64
+ - **World** - the universe where the Dynflow runs the code: it holds all
65
+ needed configuration. Usually there's only one world per Dynflow process,
66
+ besides configuration it also holds `Persistence`, `Logger`, `Executor` and
67
+ all the other objects necessary for action executing. This concept
68
+ allows us to avoid globally shared state. Also, the worlds can
69
+ talk to each other, which is helpful for production and
70
+ high-availability setups,
71
+ having multiple worlds on different hosts handle the execution of the execution plans.
72
+
73
+ ## Examples
74
+
75
+ See the
76
+ [examples directory](https://github.com/Dynflow/dynflow/tree/master/examples)
77
+ for the code in action. Running those files (except the
78
+ `example_helper.rb` file) leads to the Dynflow runtime being initialized
79
+ (including the web console where one can explore the features and
80
+ experiment).
81
+
82
+ *TODO*
83
+
84
+ - for async operations
85
+ - for orchestrating system/ssh calls
86
+ - for keeping consistency between local database and external systems
87
+ - sub-tasks
88
+
89
+ ## How to use
90
+
91
+ ### World creation TODO
92
+
93
+ - *include executor description*
94
+
95
+ ### Development vs production TODO
96
+
97
+ - *In development execution runs in the same process, in production there is an
98
+ executor process.*
99
+
100
+ ### Action anatomy
101
+
102
+ {% digraph %}
103
+ rankdir=LR
104
+ Trigger -> Plan -> Run -> Finalize
105
+ {% enddigraph %}
106
+
107
+ When action is triggered, Dynflow executes plan method on this action, which
108
+ is responsible for building the execution plan. It builds the execution plan by calling
109
+ `plan_action` and `plan_self` methods, effectively listing actions that should be run as
110
+ a part of this execution plan. In other words, it compiles a list of actions on which
111
+ method `run` will be called. Also it's responsible for giving these actions
112
+ an order. A simple example of such plan action might look like this
113
+
114
+ ```ruby
115
+ # this would plan deletion of files passed as an input array
116
+ def plan(files)
117
+ files.each do |filename|
118
+ plan_action MyActions::File::Destroy, filename
119
+ end
120
+ end
121
+ ```
122
+
123
+ Note that it does not have to be only other actions that are planned to run.
124
+ In fact it's very common that the action plan itself, which means it will
125
+ put it's own `run` method call in the execution plan. In order to do that
126
+ you can use `plan_self`. This could be used in MyActions::File::Destroy
127
+ used in previous example
128
+
129
+ ```ruby
130
+ class MyActions::File::Destroy < Dynflow::Action
131
+ def plan(filename)
132
+ plan_self path: filename
133
+ end
134
+
135
+ def run
136
+ File.rm(input.fetch(:path))
137
+ end
138
+ end
139
+ ```
140
+
141
+ In example above, it seems that `plan_self` is just shortcut to
142
+ `plan_action MyActions::File::Destroy, filename` but it's not entirely true.
143
+ Note that `plan_action` always trigger `plan` of a given action while `plan_self`
144
+ plans only the `run` of Action, so by using `plan_action` we'd end up in
145
+ endless loop.
146
+
147
+ Also note, that run method does not take any input. In fact, it can use
148
+ `input` method that refers to arguments, that were used in `plan_self`.
149
+
150
+ Similar to the input mentioned above, the run produces output.
151
+ After that some finalizing steps can be taken. Actions can use outputs of other actions
152
+ as parts of their inputs establishing dependency. Action's state is serialized between each phase
153
+ and survives machine/executor restarts.
154
+
155
+ As lightly touched in the previous paragraph there are 3 phases: `plan`, `run`, `finalize`.
156
+ Plan phase starts by triggering an action.
157
+
158
+ #### Input and Output
159
+
160
+ Both input and output are `Hash`es accessible by `Action#input` and `Action#output` methods. They
161
+ need to be serializable to JSON so it should contain only combination of primitive Ruby types
162
+ like: `Hash`, `Array`, `String`, `Integer`, etc.
163
+
164
+
165
+ {% warning_block %}
166
+
167
+ One should avoid using directly
168
+
169
+ ```ruby
170
+ self.output = { key: data }
171
+ ```
172
+
173
+ It might delete other data stored in the output (potentially by middleware and other
174
+ parts of the action). Therefore it's preferred to use
175
+
176
+ ```ruby
177
+ output.update(key: data, another_key: another_data)
178
+ # or for one key
179
+ output[:key] = data
180
+ ```
181
+
182
+ {% endwarning_block %}
183
+
184
+ {% info_block %}
185
+
186
+ You may sometime find these input/output format definitions:
187
+
188
+ ```ruby
189
+ class AnAction < Dynflow::Action
190
+ input_format do
191
+ param :id, Integer
192
+ param :name, String
193
+ end
194
+
195
+ output_format do
196
+ param :uuid, String
197
+ end
198
+ end
199
+ ```
200
+
201
+ This might me quite handy especially in combination with
202
+ [subscriptions](#subscriptions) functionality.
203
+
204
+ The format follows [apipie-params](https://github.com/iNecas/apipie-params) for more details.
205
+ Validations of input/output could be performed against this description but it's not turned on
206
+ by default. (It needs to be revisited and updated to be fully functional.)
207
+
208
+ {% endinfo_block %}
209
+
210
+ #### Triggering
211
+
212
+ Triggering the action means starting the plan phase, followed by immediate execution.
213
+ Any action is triggered by calling:
214
+
215
+ ``` ruby
216
+ world_instance.trigger(AnAction, *args)
217
+ ```
218
+
219
+ {% info_block %}
220
+
221
+ In Foreman and Katello actions are usually triggered by `ForemanTask.sync_task` and
222
+ `ForemanTasks.async_task` so following part is not that important if you are using
223
+ `ForemanTasks`.
224
+
225
+ {% endinfo_block %}
226
+
227
+ `World#trigger` method returns object of `TriggerResult` type. Which is
228
+ [Algebrick](http://blog.pitr.ch/projects/algebrick/) variant type where definition follows:
229
+
230
+ ```ruby
231
+ TriggerResult = Algebrick.type do
232
+ # Returned by #trigger when planning fails.
233
+ PlaningFailed = type { fields! execution_plan_id: String, error: Exception }
234
+ # Returned by #trigger when planning is successful but execution fails to start.
235
+ ExecutionFailed = type { fields! execution_plan_id: String, error: Exception }
236
+ # Returned by #trigger when planning is successful, #future will resolve after
237
+ # ExecutionPlan is executed.
238
+ Triggered = type { fields! execution_plan_id: String, future: Future }
239
+
240
+ variants PlaningFailed, ExecutionFailed, Triggered
241
+ end
242
+ ```
243
+
244
+ If you do not know `Algebrick` you can think about these as `Struct`s with types.
245
+ You can see how it's used to distinguish all the possible results
246
+ [in ForemanTasks module](https://github.com/theforeman/foreman-tasks/blob/master/lib/foreman_tasks.rb#L20-L32).
247
+
248
+ ```ruby
249
+ def self.trigger_task(async, action, *args, &block)
250
+ Match! async, true, false
251
+
252
+ match trigger(action, *args, &block),
253
+ # Raise if there is any error caused either by failed planning or
254
+ # by faild start of execution.
255
+ (on ::Dynflow::World::PlaningFailed.(error: ~any) |
256
+ ::Dynflow::World::ExecutionFailed.(error: ~any) do |error|
257
+ raise error
258
+ end),
259
+ # Succesfully triggered.
260
+ (on ::Dynflow::World::Triggered.(
261
+ execution_plan_id: ~any, future: ~any) do |id, finished|
262
+ # block on the finished Future if this is called synchronously
263
+ finished.wait if async == false
264
+ return ForemanTasks::Task::DynflowTask.find_by_external_id!(id)
265
+ end)
266
+ end
267
+ ```
268
+
269
+ #### Plan phase
270
+
271
+ Planning always uses the thread triggering the action. Plan phase
272
+ configures action's input for run phase. It starts by executing
273
+ `plan` method of the action instance passing in arguments from
274
+ `World#trigger method`
275
+
276
+ ```ruby
277
+ world_instance.trigger(AnAction, *args)
278
+ # executes following
279
+ an_action.plan(*args) # an_action is AnAction
280
+ ```
281
+
282
+ `plan` method is inherited from Dynflow::Action and by default it plans itself if
283
+ `run` method is present using first argument as input.
284
+
285
+ ```ruby
286
+ class AnAction < Dynflow::Action
287
+ def run
288
+ output.update self.input
289
+ end
290
+ end
291
+
292
+ world_instance.trigger AnAction, data: 'nothing'
293
+ ```
294
+
295
+ The above will just plan itself copying input to output in run phase.
296
+
297
+ In most cases the `plan` method is overridden to plan self with transformed arguments and/or
298
+ to plan other actions. In the Rails application, the arguments of the
299
+ plan phase are often the ActiveRecord objects, that are then
300
+ used to produce the inputs for the actions.
301
+
302
+ Let's look at the argument transformation first:
303
+
304
+ ```ruby
305
+ class AnAction < Dynflow::Action
306
+ def plan(any_array)
307
+ # pick just numbers
308
+ plan_self numbers: any_array.select { |v| v.is_a? Number }
309
+ end
310
+
311
+ def run
312
+ # compute sum - simulating a time consuming operation
313
+ output.update sum: input[:numbers].reduce(&:+)
314
+ end
315
+ end
316
+ ```
317
+
318
+ {% info_block %}
319
+
320
+ It's considered a good practice to use the just enough data for the
321
+ input for the action to perform the job. That means not too much
322
+ (such as using ActiveRecord's attributes), as it might have
323
+ performance impact as well as causes issues when changing the
324
+ attributes later.
325
+
326
+ On the other hand, the input should contain enough data to perform
327
+ the job without the need for reaching to external sources. Therefore,
328
+ instead of passing just the ActiveRecord id and loading the whole
329
+ record again in run phase, just to use some attributes, it's better to
330
+ use these attributes directly as input of the action.
331
+
332
+ Following theses rules should lead to the best results, both from
333
+ readability and performance point of view.
334
+
335
+ {% endinfo_block %}
336
+
337
+ Now let's see an example with action planning:
338
+
339
+ ```ruby
340
+ class SumNumbers < Dynflow::Action
341
+ def plan(numbers)
342
+ plan_self numbers: numbers
343
+ end
344
+
345
+ def run
346
+ output.update sum: input[:numbers].reduce(&:+)
347
+ end
348
+ end
349
+
350
+ class SumManyNumbers < Dynflow::Action
351
+ def plan(numbers)
352
+ # references to planned actions
353
+ planned_sub_sum_actions = numbers.each_slice(10).map do |numbers|
354
+ plan_action SumNumbers, numbers
355
+ end
356
+
357
+ # prepare array of output references where each points to sum in the
358
+ # output of particular action
359
+ sub_sums = planned_sub_sum_actions.map do |action|
360
+ action.output[:sum]
361
+ end
362
+
363
+ # plan one last action which will sum the sub_sums
364
+ # it depends on all planned_sub_sum_actions because it uses theirs outputs
365
+ plan_action SumNumbers, sub_sums
366
+ end
367
+ end
368
+
369
+ world_instance.trigger SumManyNumbers, (1..100).to_a
370
+ ```
371
+
372
+ Above example will in parallel sum numbers by slices of 10 values: first action sums `1..10`,
373
+ second action sums `11..20`, ..., tenth action sums `91..100`. After all sub sums are computed
374
+ one final action sums the sub sums into final sum.
375
+
376
+ {% warning_block %}
377
+
378
+ This example is here to demonstrate the planning abilities. In reality this parallelization of
379
+ compute intensive tasks does not have a positive effect on Dynflow running on MRI. The pool of
380
+ workers may starve. It is not a big issue since Dynflow is mainly used to orchestrate external
381
+ services.
382
+
383
+ *TODO add link to detail explanation in How it works when available.*
384
+
385
+ {% endwarning_block %}
386
+
387
+ Action may access local DB in plan phase,
388
+ see [Database and Transactions](#database-and-transactions).
389
+
390
+ #### Run phase
391
+
392
+ Actions has a run phase if there is `run` method implemented.
393
+ (There may be actions just planning other actions.)
394
+
395
+ The run method implements the main piece of work done by this action converting
396
+ input into output. Input is immutable in this phase. It's the right place for all the steps
397
+ which are likely to fail. Action `run` phase are allowed to have side effects like: file operations,
398
+ calls to other systems, etc.
399
+ Local DB should not be accessed in this phase,
400
+ see [Database and Transactions](#database-and-transactions)
401
+
402
+ #### Finale phase
403
+
404
+ Main purpose of `finalize` phase is to be able access local DB after action finishes
405
+ successfully, like: indexing based on new data, updating records as fully created, etc.
406
+ Finalize phase does not modify input or output of the action.
407
+ Action may access local DB in `finalize` phase and must be **idempotent**,
408
+ see [Database and Transactions](#database-and-transactions).
409
+
410
+ ### Dependencies
411
+
412
+ As already mentioned, actions can use output of different actions as their input (or just parts).
413
+ When they do it creates dependency between actions, which is automatically detected
414
+ by Dynflow and the execution plan is built accordingly.
415
+
416
+ ```ruby
417
+ def plan
418
+ first_action = plan_action AnAction
419
+ second_action = plan_action AnAction, first_action.output[:a_key_in_output]
420
+ end
421
+ ```
422
+
423
+ `second_action` uses part of the `first_action`'s output
424
+ therefore it depends on the `first_action`.
425
+
426
+ If actions are planned without this dependency as follows
427
+
428
+ ```ruby
429
+ def plan
430
+ first_action = plan_action AnAction
431
+ second_action = plan_action AnAction
432
+ end
433
+ ```
434
+
435
+ then they are independent and they are executed concurrently.
436
+
437
+ There is also other mechanism how to describe dependencies between actions than just
438
+ the one based on output usage. Dynflow user can specify the order between planned
439
+ actions with DSL methods `sequence` and `concurrence`. Both methods are taking blocks
440
+ and they specify how actions planned inside the block
441
+ (or inner `sequence` and `concurrence` blocks) should be executed.
442
+
443
+ By default `plan` considers it's space as inside `concurrence`. Which means
444
+
445
+ ```ruby
446
+ def plan
447
+ first_action = plan_action AnAction
448
+ second_action = plan_action AnAction
449
+ end
450
+ ```
451
+ equals
452
+
453
+ ```ruby
454
+ def plan
455
+ concurrence do
456
+ first_action = plan_action AnAction
457
+ second_action = plan_action AnAction
458
+ end
459
+ end
460
+ ```
461
+
462
+ You can establish same dependency between `first_action` and `second_action` without
463
+ using output by using `sequence`
464
+
465
+ ```ruby
466
+ def plan
467
+ sequence do
468
+ first_action = plan_action AnAction
469
+ second_action = plan_action AnAction
470
+ end
471
+ end
472
+ ```
473
+
474
+ As mentioned the `sequence` and `concurrence` methods can be nested and mixed
475
+ with output usage to create more complex dependencies. Let see commented example:
476
+
477
+ ```ruby
478
+ def plan
479
+ # Plans 3 actions of type AnAction to be executed in sequence
480
+ # argument is the index in the sequence.
481
+ actions_executed_sequentially = sequence do
482
+ 3.times.map { |i| plan_action AnAction, i }
483
+ end
484
+
485
+ # Depends on output of the last action in `actions_executed_sequentially`
486
+ # so it's added to the above sequence to be executed as 4th.
487
+ action1 = plan_action AnAction, actions_executed_sequentially.last.output
488
+
489
+ # It's planed in default plan's concurrency scope it's executed concurrently
490
+ # to about four actions.
491
+ action2 = plan_action AnAction
492
+ end
493
+ ```
494
+
495
+ The order than will be:
496
+
497
+ - concurrently:
498
+ - sequentially:
499
+ 1. `*actions_executed_sequentially`
500
+ 1. `action1`
501
+ - `action2`
502
+
503
+ Let's see one more example:
504
+
505
+ ```ruby
506
+ def plan
507
+ actions = sequence do
508
+ 2.times.map do |i|
509
+ concurrency do
510
+ 2.times.map { plan_action AnAction, i }
511
+ end
512
+ end
513
+ end
514
+ end
515
+ ```
516
+ Which results in order of execution:
517
+
518
+ - sequentially:
519
+ 1. concurrently:
520
+ - `actions[0][0]` argument: 0
521
+ - `actions[0][1]` argument: 0
522
+ 1. concurrently:
523
+ - `actions[1][0]` argument: 1
524
+ - `actions[1][1]` argument: 1
525
+
526
+ {% info_block %}
527
+
528
+ It's on our todo-list to change that to be able to define acyclic-graph of dependencies
529
+ between the actions. `sequence` and `concurrence` methods will then be deprecated and kept
530
+ just for backward compatibility.
531
+
532
+ {% endinfo_block %}
533
+
534
+ {% warning_block %}
535
+
536
+ Internally dependencies are also modeled with objects representing Sequences and Concurrences,
537
+ which makes it weaker than acyclic-graph so in some cases during the dependency resolution
538
+ it might not lead into the most effective execution plan. Some actions will run in sequence even
539
+ though they could be run concurrently. This limitation is likely to be
540
+ removed in some of the further releases.
541
+
542
+ {% endwarning_block %}
543
+
544
+ ### Database and Transactions
545
+
546
+ Dynflow was designed to help with orchestration of other services.
547
+ The usual execution looks as follows, we use an ActiveRecord User as example of a resource.
548
+
549
+ 1. Trigger user creation, argument is an unsaved ActiveRecord user object
550
+ 1. Planning: The user is stored in local DB (in the Dynflow hosting application) within the
551
+ `plan` phase. The record is marked as incomplete.
552
+ 1. Running: Operations needed for the user in external services with (e.g.) REST call.
553
+ The phase finishes when the all the external calls succeeded successfully.
554
+ 1. Finalizing: The record in local DB is marked as done: ready to be
555
+ used. Potentially, saving some data that were retrieved in the `run`
556
+ phase back to the local database.
557
+
558
+ For that reason there are transactions around whole `plan` and `finale` phase
559
+ (all action's plan methods are in one transaction).
560
+ If anything goes wrong in the `plan` phase any change made during planning to local DB is
561
+ reverted. Same holds for finalizing, if anything goes wrong, all changes are reverted. Therefore
562
+ all finalization methods has to be **idempotent**.
563
+
564
+ Internally Dynflow uses Sequel as its ORM, but users may choose what they need
565
+ to access they data. There is an interface `TransactionAdapters::Abstract` where its
566
+ implementations may provide transactions using different ORMs.
567
+ The most common one probably being `TransactionAdapters::ActiveRecord`.
568
+
569
+ So in the above example 2. and 4. step would be wrapped in `ActiveRecord` transaction
570
+ if `TransactionAdapters::ActiveRecord` is used.
571
+
572
+ Second outcome of the design is convention when actions should be accessing local Database:
573
+
574
+ - **allowed** - in `plan` and `finalize` phases
575
+ - **disallowed** - (or at least discouraged) in the `run` phase
576
+
577
+ {% warning_block %}
578
+
579
+ *TODO warning about AR pool configuration, needs to have sufficient size*
580
+
581
+ {% endwarning_block %}
582
+
583
+ ### Composition
584
+
585
+ Dynflow is designed to allow easy composition of small building blocks
586
+ called `Action`s. Typically there are actions composing smaller pieces
587
+ together and other actions doing actual steps of work as in following
588
+ example:
589
+
590
+ ```ruby
591
+ class CreateInfrastructure < Dynflow::Action
592
+ def plan
593
+ sequence do
594
+ concurrence do
595
+ plan_action(CreateMachine, 'host1', 'db')
596
+ plan_action(CreateMachine, 'host2', 'storage')
597
+ end
598
+ plan_action(CreateMachine,
599
+ 'host3',
600
+ 'web_server',
601
+ :db_machine => 'host1',
602
+ :storage_machine => 'host2')
603
+ end
604
+ end
605
+ end
606
+ ```
607
+ Action `CreateInfrastructure` does not have a `run` method defined, it only
608
+ defines `plan` action where other actions composed together.
609
+
610
+ ### Subscriptions
611
+
612
+ Even though composing actions is quite easy and allows to decompose
613
+ business logic to small pieces it does not directly support extensions
614
+ by plugins. For that there are subscriptions.
615
+
616
+ `Actions` can subscribe from a plugin, gem, any other library to already
617
+ loaded `Actions`, doing so they extend the planning process with self.
618
+
619
+ Lets look at an example starting by definition of a core action
620
+
621
+ ```ruby
622
+ # This action can be extended without doing any
623
+ # other steps to support it.
624
+ class ACoreAppAction < Dynflow::Action
625
+ def plan(arguments)
626
+ plan_self(args: arguments)
627
+ plan_action(AnotherCoreAppAction, arguments.first)
628
+ end
629
+
630
+ def run
631
+ puts "Running core action: #{input[:args]}"
632
+ self.output.update success: true
633
+ end
634
+ end
635
+ ```
636
+
637
+ followed by an action definition defined in a plugin/gem/etc.
638
+
639
+ ```ruby
640
+ class APluginAction < Dynflow::Action
641
+ # plan this action whenever ACoreAppAction action is planned
642
+ def self.subscribe
643
+ ACoreAppAction
644
+ end
645
+
646
+ def plan(arguments)
647
+ # arguments are same as in ACoreAppAction#plan
648
+ plan_self(args: arguments)
649
+ end
650
+
651
+ def run
652
+ puts "Running plugin action: #{input[:args]}"
653
+ end
654
+ end
655
+ ```
656
+
657
+ Subscribed actions are planned with same arguments as action they are
658
+ subscribing to which is called `trigger`. Their plan method is called right
659
+ after planning of the triggering action finishes.
660
+
661
+ It's also possible to access target action and use its output which
662
+ makes it dependent (running in sequence) on triggering action.
663
+
664
+ ```ruby
665
+ def plan(arguments)
666
+ plan_self trigger_success: trigger.output[:success]
667
+ end
668
+
669
+ def run
670
+ self.output.update 'trigger succeeded' if self.input[:trigger_success]
671
+ end
672
+ ```
673
+
674
+ Subscription is designed for extension by plugins, it should **not** be used
675
+ inside a single library/app-module. It would make the process definition
676
+ hard to follow (all subscribed actions would need to be looked up).
677
+
678
+ ### Suspending
679
+
680
+ Sometimes action represents tasks taken in different services,
681
+ (e.g. repository synchronization in [Pulp](http://www.pulpproject.org/)).
682
+ Dynflow tries not to waste computer resources so it offers tools to free
683
+ threads to work on other actions while waiting on external tasks or events.
684
+
685
+ Dynflow allows actions to suspend and be woken up on external events.
686
+ Lets create a simulation of an external service before showing the example
687
+ of suspending action.
688
+
689
+ ```ruby
690
+ class AnExternalService
691
+ def start_synchronization(report_to)
692
+ Thread.new do
693
+ sleep 1
694
+ report_to << :done
695
+ end
696
+ end
697
+ end
698
+ ```
699
+
700
+ The `AnExternalService` can be invoked to `start_synchronization` and it will
701
+ report back a second later to action passed in argument `report_to`. It sends
702
+ event `:done` back by `<<` method.
703
+
704
+ Lets look at an action example.
705
+
706
+ ```ruby
707
+ class AnAction < Dynflow::Action
708
+ EXTERNAL_SERVICE = AnExternalService.new
709
+
710
+ def plan
711
+ plan_self
712
+ end
713
+
714
+ def run(event)
715
+ case event
716
+ when nil # first run
717
+ suspend do |suspended_action|
718
+ EXTERNAL_SERVICE.start_synchronization suspended_action
719
+ end
720
+ when :done # external task is done
721
+ output.update success: true
722
+ # let the run phase finish normally
723
+ else
724
+ raise 'unknown event'
725
+ end
726
+ end
727
+ end
728
+ ```
729
+ Which is then executed as follows:
730
+
731
+ 1. `AnAction` is triggered
732
+ 1. It's planned.
733
+ 1. Its `run` phase begins.
734
+ 1. `run` method is invoked with no event (`nil`).
735
+ 1. Matches with case branch initiating the external synchronization.
736
+ 1. Action initializes the synchronization and pass in reference
737
+ to suspended_action.
738
+ 1. Action is suspended, execution of the run method finishes immediately
739
+ after `suspend` is called, its block parameter is evaluated right after
740
+ suspending.
741
+ 1. Action is kept on memory to be woken up when events are received but it does not
742
+ block any threads.
743
+ 1. Action receives `:done` event through suspend action reference.
744
+ 1. `run` method is executed again with `:done` event.
745
+ 1. Output is updated with `success: true` and actions finishes `run` phase.
746
+ 1. There is no `finalize` phase, action is done.
747
+
748
+ This event mechanism is quite flexible, it can be used for example to build a
749
+ [polling action abstraction](https://github.com/Dynflow/dynflow/blob/master/lib/dynflow/action/polling.rb)
750
+ which is a topic for next chapter.
751
+
752
+ ### Polling
753
+
754
+ Not all services support callbacks to be registered which would allow to wake up suspended
755
+ actions only once at the end when the external task is finished. In that case we often
756
+ need to poll the service to see if the task is still running or finished.
757
+
758
+ For that purpose there is `Polling` module in Dynflow. Any action can be turned into a polling one
759
+ just by including the module.
760
+
761
+ ```ruby
762
+ class AnAction < Dynflow::Action
763
+ include Dynflow::Action::Polling
764
+ ```
765
+
766
+ There are 3 methods need to be always implemented:
767
+
768
+ - `done?` - determines when the task is complete based on external task's data.
769
+ - `invoke_external_task` - starts the external task.
770
+ - `poll_external_task` - polls the external task status data and returns a status
771
+ (JSON serializable data like: `Hash`, `Array`, `String`, etc.) which are stored in action's
772
+ output.
773
+
774
+ ```ruby
775
+ def done?
776
+ external_task[:progress] == 1
777
+ end
778
+
779
+ def invoke_external_task
780
+ triger_the_task_with_rest_call
781
+ end
782
+
783
+ def poll_external_task
784
+ data = poll_data_with_rest_call
785
+ progress = calculate_progress data # => a float in 0..1
786
+ { progress: progress
787
+ data: data }
788
+ end
789
+ end
790
+ ```
791
+
792
+ This action will do following in run phase:
793
+
794
+ 1. `invoke_external_task` on first run of the action
795
+ 1. suspends and then periodically:
796
+ 1. wakes up
797
+ 1. `poll_external_task`
798
+ 1. checks if `done?`:
799
+ - `true` -> it concludes the run phase
800
+ - `false` -> it schedules next polling
801
+
802
+ There are 2 other methods handling external task data which can optionally overridden:
803
+
804
+ - `external_task` - reads the external task's stored data, by default it reads `self.output[:task]`
805
+ - `external_task=` - writes the the external task's stored data, by default it writes to
806
+ `self.output[:task] = value`
807
+
808
+ There are also other features implemented like:
809
+
810
+ - Gradual prolongation of the polling interval.
811
+ - Retries on a poll failing.
812
+
813
+ Please see the
814
+ [`Polling` module](https://github.com/Dynflow/dynflow/blob/master/lib/dynflow/action/polling.rb)
815
+ for more details.
816
+
817
+ ### States
818
+
819
+ Each **Action phase** can be in one of the following states:
820
+
821
+ - **Pending** - Not yet executed.
822
+ - **Running** - An action phase id being executed right now.
823
+ - **Success** - Execution of an action phase finished successfully.
824
+ - **Error** - There was an error during execution.
825
+ - **Suspended** - Only `run` phase, when action sleeps waiting for events to be woken up.
826
+ - **Skipped** - Failed actions can be marked as skipped allowing rest of the
827
+ execution plan to finish successfully.
828
+ - **Skipping** - Action is marked for skipping but execution plan was not yet
829
+ resumed to mark it as Skipped.
830
+
831
+ **Execution plan** has following states:
832
+
833
+ - **Pending** - Planning did not start yet.
834
+ - **Planning** - It's being planned.
835
+ - **Planned** - It've been planned, running phase did not start yet.
836
+ - **Running** - It's running, `run` and `finalize` phases of actions are executed.
837
+ - **Paused** - It was paused when running. Happens on error or executor restart.
838
+ - **Stopped** - Execution plan is completed.
839
+
840
+ **Execution plan** also has following results:
841
+
842
+ - **Success** - Everything finished without error or skips.
843
+ - **Warning** - When there are skipped steps.
844
+ - **Error** - When one or more actions failed.
845
+ - **Pending** - Execution plan still runs.
846
+
847
+ *TODO how do I access such states as a programmer?*
848
+ *TODO which Action phase states are "finish" and which requires user interaction?*
849
+
850
+ ### Error handling
851
+
852
+ If there is an error risen in **`plan` phase**, the error is persisted in the Action object
853
+ for later inspection and it bubbles up in `World#trigger` method which was used to trigger
854
+ the action leading to this error.
855
+ If you compare it to errors raised during `run` and `finalize` phase,
856
+ there's the major difference: Those never bubble up in `trigger` because they are running
857
+ in executor not in triggering Thread, they are just persisted in Action object.
858
+
859
+ If there is an error in **`run` phase**, the execution pauses. You can inspect the error in
860
+ [console](#console). The error may be intermittent or you may fix the problem manually. After
861
+ that the execution plan can be resumed and it'll continue by rerunning the failed action and
862
+ continuing with the rest of the actions. During fixing the problem you may also do the steps
863
+ in the actions manually, in that case the failed action can be also marked as skipped. After
864
+ resuming the skipped action is not executed and the execution plan continues with the rest.
865
+
866
+ If there is an error in **`finalize` phase**, whole `finalize` phase for all the actions is
867
+ rollbacked and can be rerun when the problem is fixed by resuming.
868
+
869
+ If you encounter an error during run phase `error!` or usual `raise` can be used.
870
+
871
+ #### Rescue strategy TODO
872
+
873
+ ### Console TODO
874
+
875
+ - *where to access*
876
+ - *screenshots*
877
+
878
+ ### Testing TODO
879
+
880
+ - *testing helper methods*
881
+ - *examples*
882
+ - *see [testing of testing](https://github.com/Dynflow/dynflow/blob/master/test/testing_test.rb)*
883
+
884
+ ### Long-running actions
885
+
886
+ Dynflow was designed as an Orchestration tool, parallelization of heavy CPU computation tasks
887
+ was not directly considered. Even with multiple executors single execution plan always runs
888
+ on one executor, so without JRuby it wont scale well (MRI's GIL). However JRuby support
889
+ should be added soon (TODO update when merged).
890
+
891
+ Another problem with long-running actions are blocked worker. Executor has only a limited pool of
892
+ workers, if more of them become busy it may result in worsen performance.
893
+
894
+ Blocking actions for long time are also problematic.
895
+
896
+ Solutions are:
897
+
898
+ - **Using action suspending** - suspending the action until a condition is met,
899
+ freeing the worker.
900
+ - **Offloading computation** - CPU heavy parts can be offloaded to different services
901
+ notifying the suspended actions when the computation is done.
902
+
903
+ ### Middleware
904
+
905
+ Each action class has chain of middlewares which wrap phases of the action execution.
906
+ It's very similar to rack middlewares.
907
+ To create new middleware inherit from `Dynflow::Middleware` class. It has 5 methods which can be
908
+ overridden: `plan`, `run`, `finalize`, `plan_phase`, `finalize_phase`. Where the default
909
+ implementation for all the methods looks as following
910
+
911
+ ```ruby
912
+ def plan(*args)
913
+ pass *args
914
+ end
915
+ ```
916
+
917
+ When overriding user can insert code before and/or after the `pass` method which executes next
918
+ middleware in the chain or the action itself which is at the end of the chain. Most usually the
919
+ `pass` is always called somewhere in the overridden method. There may be some cases when it can
920
+ be omitted, then it'll prevent all following middlewares and action from running.
921
+
922
+ Some implementation examples:
923
+ [KeepCurrentUser](https://github.com/theforeman/foreman-tasks/blob/master/app/lib/actions/middleware/keep_current_user.rb),
924
+ [Action::Progress::Calculate](https://github.com/Dynflow/dynflow/blob/master/lib/dynflow/action/progress.rb#L13-L42).
925
+
926
+ Each Action has a chain of middlewares defined. Middleware can be added by calling `use`
927
+ in the action class.
928
+
929
+ ```ruby
930
+ class AnAction < Dynflow::Action
931
+ use AMiddleware, after: AnotherMiddleware
932
+ end
933
+ ```
934
+
935
+ Method `use` understands 3 option keys:
936
+
937
+ - `:before` - makes this middleware to be ordered before a given middleware
938
+ - `:after` - makes this middleware to be ordered after a given middleware
939
+ - `:replace` - this middleware will replace given middleware
940
+
941
+ The `:before` and `:after` keys are used to build a graph from the middlewares which is then
942
+ sorted down with
943
+ [topological sort](http://ruby-doc.org//stdlib-2.0/libdoc/tsort/rdoc/TSort.html)
944
+ to the chain of middleware execution.
945
+
946
+ ### SubTasks TODO
947
+
948
+ - *when to use?*
949
+ - *how to use?*
950
+
951
+ ## How it works TODO
952
+
953
+ ### Action states TODO
954
+
955
+ - *normal phases and Present phase*
956
+ - *how to walk the execution plan*
957
+
958
+ ### Inner-world communication and multi-executors TODO
959
+
960
+ ### Thread-pools TODO
961
+
962
+ - *how it works now*
963
+ - *how it'll work*
964
+ - *gotchas*
965
+ - *worker pool sizing*
966
+
967
+ ### Suspending -> events TODO
968
+
969
+ ## Use cases TODO
970
+
971
+ - *Embedded without a DB, like inside CLI tool for a complex installation*
972
+ - *reserve resources in planning do not try to do `if`s in run phase*
973
+ - *Projects: katello, foreman, staypuft, fusor*
974
+
975
+ ## Comments
976
+
977
+ **Comments are temporally turned on here for faster feedback.**