dynflow 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
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.**