commit 9936a38843ac9bbd1b3a9db4a2dca87282433148 Author: Benjamin ter Kuile Date: Thu Dec 21 11:51:55 2017 +0100 initial github commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..219985c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.ember-cli b/.ember-cli new file mode 100644 index 0000000..ee64cfe --- /dev/null +++ b/.ember-cli @@ -0,0 +1,9 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b3e41aa --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module' + }, + extends: 'eslint:recommended', + env: { + browser: true + }, + globals: { + moment: true, + t: true, + is_translation_path: true + }, + rules: { + 'no-cond-assign': 0 + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fa39a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules +/bower_components + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log* +yarn-error.log +testem.log + +# ember-try +.node_modules.ember-try/ +bower.json.ember-try +package.json.ember-try diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..889b2bf --- /dev/null +++ b/.npmignore @@ -0,0 +1,16 @@ +/bower_components +/config/ember-try.js +/dist +/tests +/tmp +**/.gitkeep +.bowerrc +.editorconfig +.ember-cli +.gitignore +.eslintrc.js +.watchmanconfig +.travis.yml +bower.json +ember-cli-build.js +testem.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d04ff8a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +--- +language: node_js +node_js: + # we recommend testing addons with the same minimum supported node version as Ember CLI + # so that your addon works for all apps + - "4" + +sudo: false +dist: trusty + +addons: + chrome: stable + +cache: + directories: + - $HOME/.npm + +env: + # we recommend new addons test the current and previous LTS + # as well as latest stable release (bonus points to beta/canary) + - EMBER_TRY_SCENARIO=ember-lts-2.8 + - EMBER_TRY_SCENARIO=ember-lts-2.12 + - EMBER_TRY_SCENARIO=ember-release + - EMBER_TRY_SCENARIO=ember-beta + - EMBER_TRY_SCENARIO=ember-canary + - EMBER_TRY_SCENARIO=ember-default + +matrix: + fast_finish: true + allow_failures: + - env: EMBER_TRY_SCENARIO=ember-canary + +before_install: + - npm config set spin false + - npm install -g npm@4 + - npm --version + +script: + # Usually, it's ok to finish the test scenario without reverting + # to the addon's original dependency state, skipping "cleanup". + - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..e7834e3 --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0d5e3ef --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1120c48 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ember-panda + +This README outlines the details of collaborating on this Ember addon. + +## Installation + +* `git clone ` this repository +* `cd ember-panda` +* `npm install` + +## Running + +* `ember serve` +* Visit your app at [http://localhost:4200](http://localhost:4200). + +## Running Tests + +* `npm test` (Runs `ember try:each` to test your addon against multiple Ember versions) +* `ember test` +* `ember test --server` + +## Building + +* `ember build` + +For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/addon/.gitkeep b/addon/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/addon/components/action-list/organization-filter.coffee b/addon/components/action-list/organization-filter.coffee new file mode 100644 index 0000000..a042aaf --- /dev/null +++ b/addon/components/action-list/organization-filter.coffee @@ -0,0 +1,23 @@ +import Ember from 'ember' + +export default Ember.Component.extend + tagName: 'span' + organizations: [] + actions: + apply_filters: -> + @get('apply_filters')?() + set_scope_organization: (organization) -> + if organization? + #organization.reload().then => + organization_id = organization?.get('id') || '' + @set 'scope_organization', organization + @set 'filters.workflow_action_definition_organization_id', organization_id + @$('.select-organization .dropdown.button').addClass('orange') + @send 'apply_filters' + else + @setProperties + scope_organization: null + scope_scenario: null + 'filters.workflow_action_definition_organization_id': '' + @$('.select-organization .dropdown.button').removeClass('orange') + @send 'apply_filters' diff --git a/addon/components/action-list/organization-filter.js b/addon/components/action-list/organization-filter.js new file mode 100644 index 0000000..0785ec7 --- /dev/null +++ b/addon/components/action-list/organization-filter.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'span', + organizations: [], + actions: { + apply_filters: function() { + var base; + return typeof (base = this.get('apply_filters')) === "function" ? base() : void 0; + }, + set_scope_organization: function(organization) { + var organization_id; + if (organization != null) { + organization_id = (organization != null ? organization.get('id') : void 0) || ''; + this.set('scope_organization', organization); + this.set('filters.workflow_action_definition_organization_id', organization_id); + this.$('.select-organization .dropdown.button').addClass('orange'); + return this.send('apply_filters'); + } else { + this.setProperties({ + scope_organization: null, + scope_scenario: null, + 'filters.workflow_action_definition_organization_id': '' + }, this.$('.select-organization .dropdown.button').removeClass('orange')); + return this.send('apply_filters'); + } + } + } +}); diff --git a/addon/components/action-list/project-filter.coffee b/addon/components/action-list/project-filter.coffee new file mode 100644 index 0000000..53d74b9 --- /dev/null +++ b/addon/components/action-list/project-filter.coffee @@ -0,0 +1,42 @@ +import Ember from 'ember' +{get} = Ember + +export default Ember.Component.extend + tagName: 'span' + scope_project: null + scope_scenario: null + projects: [] + actions: + apply_filters: -> + @get('apply_filters')?() + + edit_project_instance_action: (project_instance_action) -> + @modal 'project/edit-instance-action', model: project_instance_action + set_scope_project: (project) -> + if project? + project.reload().then => + project_id = project?.get('id') || '' + @set 'scope_project', project + @set 'filters.workflow_action_definition_title_cont', '' + @set 'filters.project_id', project_id + @$('.select-project .dropdown.button').addClass('orange') + @send 'apply_filters' + else + @setProperties + scope_project: null + scope_scenario: null + 'filters.project_id': '' + 'filters.project_instance_project_scenario_id': '' + @$('.select-project .dropdown.button').removeClass('orange') + @send 'apply_filters' + + set_scope_scenario: (project_scenario) -> + if project_scenario + scenario_id = get(project_scenario, 'id') + @$('.select-project-scenario .dropdown.button').addClass('orange') + else + @$('.select-project-scenario .dropdown.button').removeClass('orange') + scenario_id = '' + @set 'filters.project_instance_project_scenario_id', scenario_id + @set 'scope_scenario', project_scenario + @send 'apply_filters' diff --git a/addon/components/action-list/project-filter.js b/addon/components/action-list/project-filter.js new file mode 100644 index 0000000..6241d84 --- /dev/null +++ b/addon/components/action-list/project-filter.js @@ -0,0 +1,60 @@ +// Generated by CoffeeScript 1.12.5 +var get; + +import Ember from 'ember'; + +get = Ember.get; + +export default Ember.Component.extend({ + tagName: 'span', + scope_project: null, + scope_scenario: null, + projects: [], + actions: { + apply_filters: function() { + var base; + return typeof (base = this.get('apply_filters')) === "function" ? base() : void 0; + }, + edit_project_instance_action: function(project_instance_action) { + return this.modal('project/edit-instance-action', { + model: project_instance_action + }); + }, + set_scope_project: function(project) { + if (project != null) { + return project.reload().then((function(_this) { + return function() { + var project_id; + project_id = (project != null ? project.get('id') : void 0) || ''; + _this.set('scope_project', project); + _this.set('filters.workflow_action_definition_title_cont', ''); + _this.set('filters.project_id', project_id); + _this.$('.select-project .dropdown.button').addClass('orange'); + return _this.send('apply_filters'); + }; + })(this)); + } else { + this.setProperties({ + scope_project: null, + scope_scenario: null, + 'filters.project_id': '', + 'filters.project_instance_project_scenario_id': '' + }, this.$('.select-project .dropdown.button').removeClass('orange')); + return this.send('apply_filters'); + } + }, + set_scope_scenario: function(project_scenario) { + var scenario_id; + if (project_scenario) { + scenario_id = get(project_scenario, 'id'); + this.$('.select-project-scenario .dropdown.button').addClass('orange'); + } else { + this.$('.select-project-scenario .dropdown.button').removeClass('orange'); + scenario_id = ''; + } + this.set('filters.project_instance_project_scenario_id', scenario_id); + this.set('scope_scenario', project_scenario); + return this.send('apply_filters'); + } + } +}); diff --git a/addon/components/panda-image.coffee b/addon/components/panda-image.coffee new file mode 100644 index 0000000..cffee92 --- /dev/null +++ b/addon/components/panda-image.coffee @@ -0,0 +1,13 @@ +import Ember from 'ember' +import config from 'ember-get-config' + +export default Ember.Component.extend + tagName: 'img' + init: -> + src = if config.environment is 'development' or config.environment is 'test' + "#{config.rootURL}ember-panda/assets/#{@get('path')}" + else + "#{config.rootURL}assets/#{@get('path')}" + @set 'full_src', src + @_super arguments... + attributeBindings: ['full_src:src'] diff --git a/addon/components/panda-image.js b/addon/components/panda-image.js new file mode 100644 index 0000000..c127f3e --- /dev/null +++ b/addon/components/panda-image.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +import config from 'ember-get-config'; + +export default Ember.Component.extend({ + tagName: 'img', + init: function() { + var src; + src = config.environment === 'development' || config.environment === 'test' ? config.rootURL + "ember-panda/assets/" + (this.get('path')) : config.rootURL + "assets/" + (this.get('path')); + this.set('full_src', src); + return this._super.apply(this, arguments); + }, + attributeBindings: ['full_src:src'] +}); diff --git a/addon/components/project/edit-active-project-scenario.coffee b/addon/components/project/edit-active-project-scenario.coffee new file mode 100644 index 0000000..78b9bd9 --- /dev/null +++ b/addon/components/project/edit-active-project-scenario.coffee @@ -0,0 +1,18 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'small', 'buttons', 'active-project-scenario-selector-component'] + project: null + active_project_scenario: null + + actions: + show_project_scenarios_grid: (project) -> + @modal 'project/project-scenarios-show-grid', + model: project + edit_selected_project_scenario: -> + return unless project_scenario = @get('active_project_scenario') + @modal 'project/edit-project-scenario', + model: project_scenario + closeOnOverlay: false + destroy_callback: => + @set 'active_project_scenario', null diff --git a/addon/components/project/edit-active-project-scenario.js b/addon/components/project/edit-active-project-scenario.js new file mode 100644 index 0000000..7a60cec --- /dev/null +++ b/addon/components/project/edit-active-project-scenario.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['ui', 'small', 'buttons', 'active-project-scenario-selector-component'], + project: null, + active_project_scenario: null, + actions: { + show_project_scenarios_grid: function(project) { + return this.modal('project/project-scenarios-show-grid', { + model: project + }); + }, + edit_selected_project_scenario: function() { + var project_scenario; + if (!(project_scenario = this.get('active_project_scenario'))) { + return; + } + return this.modal('project/edit-project-scenario', { + model: project_scenario, + closeOnOverlay: false, + destroy_callback: (function(_this) { + return function() { + return _this.set('active_project_scenario', null); + }; + })(this) + }); + } + } +}); diff --git a/addon/components/project/instance-action-value-display.coffee b/addon/components/project/instance-action-value-display.coffee new file mode 100644 index 0000000..3cef8a9 --- /dev/null +++ b/addon/components/project/instance-action-value-display.coffee @@ -0,0 +1,53 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['project-instance-action-value-display'] + eventIO: null + project_instance_action: null + value_type: Ember.computed.alias 'project_instance_action.workflow_action_definition.value_type' + save: -> + + # https://stackoverflow.com/questions/26967855/handling-click-and-doubleclick-on-same-view-in-emberjs + doubleClick: -> + eventIO = @get 'eventIO' + if eventIO? + Ember.run.cancel eventIO + @set 'eventIO', null + + if @get('value_type') is 'boolean' + #@toggleProperty 'project_instance_action.boolean_value' + #@get('project_instance_action').save() + + # always modal, otherwise implement authorizations overhere + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + else + # fallback to single click behaviour + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + + + click: -> + if @get('eventIO') is null + eventIO = Ember.run.later @, -> + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + if @get('eventIO') isnt null + Ember.run.cancel @get('eventIO') + @set 'eventIO', null + , 500 + @set 'eventIO', eventIO + + actions: + right_click: -> + debugger + false + #edit_record: -> + # @modal 'project/edit-instance-action', model: @get('project_instance_action') + # false diff --git a/addon/components/project/instance-action-value-display.js b/addon/components/project/instance-action-value-display.js new file mode 100644 index 0000000..cde28ed --- /dev/null +++ b/addon/components/project/instance-action-value-display.js @@ -0,0 +1,54 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['project-instance-action-value-display'], + eventIO: null, + project_instance_action: null, + value_type: Ember.computed.alias('project_instance_action.workflow_action_definition.value_type'), + save: function() {}, + doubleClick: function() { + var eventIO; + eventIO = this.get('eventIO'); + if (eventIO != null) { + Ember.run.cancel(eventIO); + this.set('eventIO', null); + } + if (this.get('value_type') === 'boolean') { + return this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + } else { + return this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + } + }, + click: function() { + var eventIO; + if (this.get('eventIO') === null) { + eventIO = Ember.run.later(this, function() { + this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + if (this.get('eventIO') !== null) { + Ember.run.cancel(this.get('eventIO')); + return this.set('eventIO', null); + } + }, 500); + return this.set('eventIO', eventIO); + } + }, + actions: { + right_click: function() { + debugger; + return false; + } + } +}); diff --git a/addon/components/state-badge.coffee b/addon/components/state-badge.coffee new file mode 100644 index 0000000..d5886d2 --- /dev/null +++ b/addon/components/state-badge.coffee @@ -0,0 +1,27 @@ +import Ember from 'ember' + +Component = Ember.Component.extend + tagName: 'span' + classNames: ['ui', 'label', 'state-badge'] + classNameBindings: ['ui_class', 'having_action:with-action', 'basic:basic'] + action: null + state: '…' + human_state: Ember.computed 'state', -> + return '-' unless state = @get('state') + t("attributes.state.#{state}", default: state) + having_action: Ember.computed.bool 'action' + click: -> + @action?() + ui_class: Ember.computed 'state', -> + switch @get('state') + when 'overdue' then 'yellow' + when 'completed', 'all_completed' then 'green' + when 'rejected', 'any_rejected' then 'red' + when 'active', 'processing' then 'blue' + when 'inquiry' then 'pink' + else 'grey' + +Component.reopenClass + positionalParams: ['state'] + +export default Component diff --git a/addon/components/state-badge.js b/addon/components/state-badge.js new file mode 100644 index 0000000..b03abb3 --- /dev/null +++ b/addon/components/state-badge.js @@ -0,0 +1,50 @@ +// Generated by CoffeeScript 1.12.5 +var Component; + +import Ember from 'ember'; + +Component = Ember.Component.extend({ + tagName: 'span', + classNames: ['ui', 'label', 'state-badge'], + classNameBindings: ['ui_class', 'having_action:with-action', 'basic:basic'], + action: null, + state: '…', + human_state: Ember.computed('state', function() { + var state; + if (!(state = this.get('state'))) { + return '-'; + } + return t("attributes.state." + state, { + "default": state + }); + }), + having_action: Ember.computed.bool('action'), + click: function() { + return typeof this.action === "function" ? this.action() : void 0; + }, + ui_class: Ember.computed('state', function() { + switch (this.get('state')) { + case 'overdue': + return 'yellow'; + case 'completed': + case 'all_completed': + return 'green'; + case 'rejected': + case 'any_rejected': + return 'red'; + case 'active': + case 'processing': + return 'blue'; + case 'inquiry': + return 'pink'; + default: + return 'grey'; + } + }) +}); + +Component.reopenClass({ + positionalParams: ['state'] +}); + +export default Component; diff --git a/addon/components/ui-multi-select.coffee b/addon/components/ui-multi-select.coffee new file mode 100644 index 0000000..5609528 --- /dev/null +++ b/addon/components/ui-multi-select.coffee @@ -0,0 +1,13 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui-multi-select-container'] + labelProperty: 'name' + placeholder: 'Select' # can be a text or translation string: organization.select_users.placeholder => # 'Select ${models.plural.user} (%{count})' + options: [] + selected: [] + placeholder_text: Ember.computed 'placeholder', -> + placeholder = @get('placeholder') + placeholder = t(placeholder, count: @get('options.length')) if is_translation_path(placeholder) + placeholder.htmlSafe() + diff --git a/addon/components/ui-multi-select.js b/addon/components/ui-multi-select.js new file mode 100644 index 0000000..a044f6a --- /dev/null +++ b/addon/components/ui-multi-select.js @@ -0,0 +1,20 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['ui-multi-select-container'], + labelProperty: 'name', + placeholder: 'Select', + options: [], + selected: [], + placeholder_text: Ember.computed('placeholder', function() { + var placeholder; + placeholder = this.get('placeholder'); + if (is_translation_path(placeholder)) { + placeholder = t(placeholder, { + count: this.get('options.length') + }); + } + return placeholder.htmlSafe(); + }) +}); diff --git a/addon/components/value-type-display.coffee b/addon/components/value-type-display.coffee new file mode 100644 index 0000000..b9ccf0f --- /dev/null +++ b/addon/components/value-type-display.coffee @@ -0,0 +1,34 @@ +import Ember from 'ember' + +export default Ember.Component.extend + tagName: 'span' + type: null + classNames: ['value-type-display'] + classNameBindings: ['typeClass'] + typeClass: Ember.computed 'type', -> @get('type') || 'no-typ' + iconClass: Ember.computed 'type', -> + switch @get('type') + when 'boolean' then 'checkmark box' + when 'date' then 'calendar' + when 'select' then 'list' + when 'string' then 'terminal' + when 'text' then 'sticky note outline' + when 'number' then 'hashtag' + when 'timestamp' then 'wait' + else 'red warning circle' +# &.date +# @extend .fa-calendar +# &.number +# @extend .fa-hashtag +# &.select +# @extend .fa-list-ul +# &.string +# @extend .fa-terminal +# &.text +# @extend .fa-sticky-note-o +# &.window_from +# @extend .fa-clock-o +# &.window_to +# @extend .fa-clock-o +# &.timestamp +# @extend .fa-clock-o diff --git a/addon/components/value-type-display.js b/addon/components/value-type-display.js new file mode 100644 index 0000000..e65ef4b --- /dev/null +++ b/addon/components/value-type-display.js @@ -0,0 +1,32 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'span', + type: null, + classNames: ['value-type-display'], + classNameBindings: ['typeClass'], + typeClass: Ember.computed('type', function() { + return this.get('type') || 'no-typ'; + }), + iconClass: Ember.computed('type', function() { + switch (this.get('type')) { + case 'boolean': + return 'checkmark box'; + case 'date': + return 'calendar'; + case 'select': + return 'list'; + case 'string': + return 'terminal'; + case 'text': + return 'sticky note outline'; + case 'number': + return 'hashtag'; + case 'timestamp': + return 'wait'; + default: + return 'red warning circle'; + } + }) +}); diff --git a/addon/controllers/action-list.coffee b/addon/controllers/action-list.coffee new file mode 100644 index 0000000..a8c9ec1 --- /dev/null +++ b/addon/controllers/action-list.coffee @@ -0,0 +1,48 @@ +import Ember from 'ember' +import config from 'ember-get-config' + +export default Ember.Controller.extend + session: Ember.inject.service('session') + filters: {} + scope_project: null + scope_scenario: null + scope_organization: null + filter_query: Ember.computed 'filters.filter_calls', -> + @get('filters')?.toProperties() || {} + export_human_url: Ember.computed 'filters.filter_calls', -> + model_name = 'panda/project-instance-action' + base_url = @store.adapterFor(model_name).buildURL(model_name) + filter_addition = $.param q: @get('filter_query') + "#{base_url}/export_human.csv?#{filter_addition}" + export_technical_url: Ember.computed 'filters.filter_calls', -> + model_name = 'panda/project-instance-action' + base_url = @store.adapterFor(model_name).buildURL(model_name) + filter_addition = $.param q: @get('filter_query') + "#{base_url}/export_technical.csv?#{filter_addition}" + apply_filters: -> + @incrementProperty 'filters.filter_calls' + @set 'model.project_instance_actions.page', 1 + @set 'model.project_instance_actions.lastPage', null + @get('model.project_instance_actions').setOtherParam('q', @get('filter_query')) + false + actions: + refresh_list: -> @apply_filters() + edit_project_instance_action: (project_instance_action) -> + @modal 'project/edit-instance-action', model: project_instance_action + + toggle_filter: (key, value, checked) -> + new_value = [] + if checked + new_value = new_value.concat(@get("filters.#{key}")).concat(value) + else + @get("filters.#{key}").forEach (present_in_filter) -> + new_value.push present_in_filter unless present_in_filter is value + @set "filters.#{key}", new_value + @apply_filters() + clear_filter: (keys...) -> + keys.forEach (key) => + if Ember.isArray(@get("filters.#{key}")) + @set "filters.#{key}", [] + else + @set "filters.#{key}", null + @apply_filters() diff --git a/addon/controllers/action-list.js b/addon/controllers/action-list.js new file mode 100644 index 0000000..117e2b1 --- /dev/null +++ b/addon/controllers/action-list.js @@ -0,0 +1,82 @@ +// Generated by CoffeeScript 1.12.5 +var slice = [].slice; + +import Ember from 'ember'; + +import config from 'ember-get-config'; + +export default Ember.Controller.extend({ + session: Ember.inject.service('session'), + filters: {}, + scope_project: null, + scope_scenario: null, + scope_organization: null, + filter_query: Ember.computed('filters.filter_calls', function() { + var ref; + return ((ref = this.get('filters')) != null ? ref.toProperties() : void 0) || {}; + }), + export_human_url: Ember.computed('filters.filter_calls', function() { + var base_url, filter_addition, model_name; + model_name = 'panda/project-instance-action'; + base_url = this.store.adapterFor(model_name).buildURL(model_name); + filter_addition = $.param({ + q: this.get('filter_query') + }); + return base_url + "/export_human.csv?" + filter_addition; + }), + export_technical_url: Ember.computed('filters.filter_calls', function() { + var base_url, filter_addition, model_name; + model_name = 'panda/project-instance-action'; + base_url = this.store.adapterFor(model_name).buildURL(model_name); + filter_addition = $.param({ + q: this.get('filter_query') + }); + return base_url + "/export_technical.csv?" + filter_addition; + }), + apply_filters: function() { + this.incrementProperty('filters.filter_calls'); + this.set('model.project_instance_actions.page', 1); + this.set('model.project_instance_actions.lastPage', null); + this.get('model.project_instance_actions').setOtherParam('q', this.get('filter_query')); + return false; + }, + actions: { + refresh_list: function() { + return this.apply_filters(); + }, + edit_project_instance_action: function(project_instance_action) { + return this.modal('project/edit-instance-action', { + model: project_instance_action + }); + }, + toggle_filter: function(key, value, checked) { + var new_value; + new_value = []; + if (checked) { + new_value = new_value.concat(this.get("filters." + key)).concat(value); + } else { + this.get("filters." + key).forEach(function(present_in_filter) { + if (present_in_filter !== value) { + return new_value.push(present_in_filter); + } + }); + } + this.set("filters." + key, new_value); + return this.apply_filters(); + }, + clear_filter: function() { + var keys; + keys = 1 <= arguments.length ? slice.call(arguments, 0) : []; + keys.forEach((function(_this) { + return function(key) { + if (Ember.isArray(_this.get("filters." + key))) { + return _this.set("filters." + key, []); + } else { + return _this.set("filters." + key, null); + } + }; + })(this)); + return this.apply_filters(); + } + } +}); diff --git a/addon/mixins/project/active-workflow-action-definitions.coffee b/addon/mixins/project/active-workflow-action-definitions.coffee new file mode 100644 index 0000000..cf16473 --- /dev/null +++ b/addon/mixins/project/active-workflow-action-definitions.coffee @@ -0,0 +1,44 @@ +import Ember from 'ember' + +export default Ember.Mixin.create + selected_project_scenarios: [] # null + selected_project_scenarios_set_operation: 'union' # union or intersection + active_workflow_action_definitions: [] + active_workflow_action_definition_ids: [] + + set_active_workflow_action_definitions: -> + # initialize unused working workflow-action-definitions + if @get('selected_project_scenarios.length') + if @get('selected_project_scenarios_set_operation') is 'intersection' + @set_active_workflow_action_definitions_using_intersection() + else + @set_active_workflow_action_definitions_using_union() + else + @set 'active_workflow_action_definitions', @get('project.workflow_action_definitions') || [] + @set 'active_workflow_action_definition_ids', @get('active_workflow_action_definitions').mapBy('id').toArray() + + set_active_workflow_action_definitions_using_union: -> + selected_project_scenario_workflow_action_definition_ids = [] + working_workflow_action_definitions = [] + @get('selected_project_scenarios').forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_action_definitions').forEach (workflow_action_definition) -> + if selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0 + selected_project_scenario_workflow_action_definition_ids.push workflow_action_definition.get('id') + working_workflow_action_definitions.push workflow_action_definition + @set 'active_workflow_action_definitions', working_workflow_action_definitions + @set 'active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids + + set_active_workflow_action_definitions_using_intersection: -> + selected_project_scenario_workflow_action_definition_ids = [] + first_run = true + @get('selected_project_scenarios').forEach (selected_project_scenario) -> + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids + first_run = false + return + selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter (id) -> id in current_workflow_action_definition_ids + + @set 'active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids + @set 'active_workflow_action_definitions', @get('project.workflow_action_definitions').filter((r) -> r.get('id') in selected_project_scenario_workflow_action_definition_ids) + diff --git a/addon/mixins/project/active-workflow-action-definitions.js b/addon/mixins/project/active-workflow-action-definitions.js new file mode 100644 index 0000000..4c2ff89 --- /dev/null +++ b/addon/mixins/project/active-workflow-action-definitions.js @@ -0,0 +1,60 @@ +// Generated by CoffeeScript 1.12.5 +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +export default Ember.Mixin.create({ + selected_project_scenarios: [], + selected_project_scenarios_set_operation: 'union', + active_workflow_action_definitions: [], + active_workflow_action_definition_ids: [], + set_active_workflow_action_definitions: function() { + if (this.get('selected_project_scenarios.length')) { + if (this.get('selected_project_scenarios_set_operation') === 'intersection') { + return this.set_active_workflow_action_definitions_using_intersection(); + } else { + return this.set_active_workflow_action_definitions_using_union(); + } + } else { + this.set('active_workflow_action_definitions', this.get('project.workflow_action_definitions') || []); + return this.set('active_workflow_action_definition_ids', this.get('active_workflow_action_definitions').mapBy('id').toArray()); + } + }, + set_active_workflow_action_definitions_using_union: function() { + var selected_project_scenario_workflow_action_definition_ids, working_workflow_action_definitions; + selected_project_scenario_workflow_action_definition_ids = []; + working_workflow_action_definitions = []; + this.get('selected_project_scenarios').forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + if (selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0) { + selected_project_scenario_workflow_action_definition_ids.push(workflow_action_definition.get('id')); + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + }); + this.set('active_workflow_action_definitions', working_workflow_action_definitions); + return this.set('active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids); + }, + set_active_workflow_action_definitions_using_intersection: function() { + var first_run, selected_project_scenario_workflow_action_definition_ids; + selected_project_scenario_workflow_action_definition_ids = []; + first_run = true; + this.get('selected_project_scenarios').forEach(function(selected_project_scenario) { + var current_workflow_action_definition_ids; + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter(function(id) { + return indexOf.call(current_workflow_action_definition_ids, id) >= 0; + }); + }); + this.set('active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids); + return this.set('active_workflow_action_definitions', this.get('project.workflow_action_definitions').filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_action_definition_ids, ref) >= 0; + })); + } +}); diff --git a/addon/mixins/project/active-workflow-groups.coffee b/addon/mixins/project/active-workflow-groups.coffee new file mode 100644 index 0000000..631cd6d --- /dev/null +++ b/addon/mixins/project/active-workflow-groups.coffee @@ -0,0 +1,46 @@ +import Ember from 'ember' + +export default Ember.Mixin.create + get_active_workflow_groups: (workflow_groups, project_scenarios, set_operation = 'union')-> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_groups = workflow_groups.toArray() + # initialize unused working workflow-groups + if project_scenarios.length + if set_operation is 'intersection' + @get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios) + else + @get_active_workflow_groups_using_union(project_scenarios) + else + workflow_groups + + #@set 'active_workflow_groups', workflow_groups || [] + #@set 'active_workflow_group_ids', @get('active_workflow_groups').mapBy('id').toArray() + + get_active_workflow_groups_using_union: (project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + working_workflow_groups = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_groups').forEach (workflow_group) -> + if selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0 + selected_project_scenario_workflow_group_ids.push workflow_group.get('id') + working_workflow_groups.push workflow_group + #working_workflow_groups = working_workflow_groups + #@set 'active_workflow_groups', working_workflow_groups + #@set 'active_workflow_group_ids', selected_project_scenario_workflow_group_ids + working_workflow_groups + + get_active_workflow_groups_using_intersection: (workflow_groups, project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_group_ids = current_workflow_group_ids + first_run = false + return + selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter (id) -> id in current_workflow_group_ids + + #@set 'active_workflow_group_ids', selected_project_scenario_workflow_group_ids + #@set 'active_workflow_groups', workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + diff --git a/addon/mixins/project/active-workflow-groups.js b/addon/mixins/project/active-workflow-groups.js new file mode 100644 index 0000000..8870d7e --- /dev/null +++ b/addon/mixins/project/active-workflow-groups.js @@ -0,0 +1,58 @@ +// Generated by CoffeeScript 1.12.5 +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +export default Ember.Mixin.create({ + get_active_workflow_groups: function(workflow_groups, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_groups = workflow_groups.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return this.get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios); + } else { + return this.get_active_workflow_groups_using_union(project_scenarios); + } + } else { + return workflow_groups; + } + }, + get_active_workflow_groups_using_union: function(project_scenarios) { + var selected_project_scenario_workflow_group_ids, working_workflow_groups; + selected_project_scenario_workflow_group_ids = []; + working_workflow_groups = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_groups').forEach(function(workflow_group) { + if (selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0) { + selected_project_scenario_workflow_group_ids.push(workflow_group.get('id')); + return working_workflow_groups.push(workflow_group); + } + }); + }); + return working_workflow_groups; + }, + get_active_workflow_groups_using_intersection: function(workflow_groups, project_scenarios) { + var first_run, selected_project_scenario_workflow_group_ids; + selected_project_scenario_workflow_group_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_group_ids; + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_group_ids = current_workflow_group_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter(function(id) { + return indexOf.call(current_workflow_group_ids, id) >= 0; + }); + }); + return workflow_groups.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_group_ids, ref) >= 0; + }); + } +}); diff --git a/addon/models/panda/action-event.coffee b/addon/models/panda/action-event.coffee new file mode 100644 index 0000000..04179c6 --- /dev/null +++ b/addon/models/panda/action-event.coffee @@ -0,0 +1,12 @@ +import DS from 'ember-data' + +export default DS.Model.extend + client_identifier: DS.attr('string') + identifier: DS.attr('string') + event_key: DS.attr('string') + event_value: DS.attr('string') + event_timestamp: DS.attr('moment') + source: DS.attr('string') + + project_instance_action: DS.belongsTo('panda/project-instance-action', inverse: 'events') + diff --git a/addon/models/panda/action-event.js b/addon/models/panda/action-event.js new file mode 100644 index 0000000..f19cdaa --- /dev/null +++ b/addon/models/panda/action-event.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + client_identifier: DS.attr('string'), + identifier: DS.attr('string'), + event_key: DS.attr('string'), + event_value: DS.attr('string'), + event_timestamp: DS.attr('moment'), + source: DS.attr('string'), + project_instance_action: DS.belongsTo('panda/project-instance-action', { + inverse: 'events' + }) +}); diff --git a/addon/models/panda/project-instance-action-connection.coffee b/addon/models/panda/project-instance-action-connection.coffee new file mode 100644 index 0000000..7b95eb6 --- /dev/null +++ b/addon/models/panda/project-instance-action-connection.coffee @@ -0,0 +1,7 @@ +import DS from 'ember-data' + +export default DS.Model.extend + project_instance_action: DS.belongsTo('panda/project-instance-action') + base_project_instance_action: DS.belongsTo('panda/project-instance-action', inverse: 'pointing_project_instance_action_connections') + conditional_base_value: DS.attr('string') + connection_type: DS.attr('string') diff --git a/addon/models/panda/project-instance-action-connection.js b/addon/models/panda/project-instance-action-connection.js new file mode 100644 index 0000000..9644c3b --- /dev/null +++ b/addon/models/panda/project-instance-action-connection.js @@ -0,0 +1,11 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + project_instance_action: DS.belongsTo('panda/project-instance-action'), + base_project_instance_action: DS.belongsTo('panda/project-instance-action', { + inverse: 'pointing_project_instance_action_connections' + }), + conditional_base_value: DS.attr('string'), + connection_type: DS.attr('string') +}); diff --git a/addon/models/panda/workflow-action-connection.coffee b/addon/models/panda/workflow-action-connection.coffee new file mode 100644 index 0000000..e0e0a81 --- /dev/null +++ b/addon/models/panda/workflow-action-connection.coffee @@ -0,0 +1,8 @@ +import DS from 'ember-data' + +export default DS.Model.extend + conditional_base_value: DS.attr('string', defaultValue: '') + connection_type: DS.attr('string') # attached, constraint, info + + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_connections') + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'pointing_workflow_action_connections') diff --git a/addon/models/panda/workflow-action-connection.js b/addon/models/panda/workflow-action-connection.js new file mode 100644 index 0000000..64a87e0 --- /dev/null +++ b/addon/models/panda/workflow-action-connection.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + conditional_base_value: DS.attr('string', { + defaultValue: '' + }), + connection_type: DS.attr('string'), + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'workflow_action_connections' + }), + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'pointing_workflow_action_connections' + }) +}); diff --git a/addon/models/panda/workflow-action-definition.coffee b/addon/models/panda/workflow-action-definition.coffee new file mode 100644 index 0000000..7a601e4 --- /dev/null +++ b/addon/models/panda/workflow-action-definition.coffee @@ -0,0 +1,50 @@ +import Ember from 'ember' +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +export value_type_options = ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp'] +Model = + identifier: DS.attr('string') + title: DS.attr('string') + name: Ember.computed.alias 'title' + description: DS.attr('string') + isEditing: DS.attr('boolean', defaultValue: false) #TODO: remove, is from PMBC + done: DS.attr('boolean', defaultValue: false) #TODO: remove, is from PMBC + position: DS.attr('number', defaultValue: 0) + value_type: DS.attr('string', defaultValue: 'boolean') # means checkbox progress type + value_type_select_options: DS.attr(defaultValue: -> []) + extra_info: DS.attr('string') + offset_type: DS.attr('string', defaultValue: 'none') # none, attached, configured + overdue_coulance: DS.attr('number', defaultValue: 0) + completes_group: DS.attr('boolean', defaultValue: false) + + # Offset tree + #action_offset_base: DS.belongsTo('workflow-action-definition', inverse: 'action_offset_dependents') + #action_offset_dependents: DS.hasMany('workflow-action-definition', inverse: null) # do not give with api, will clear inverse if not given + + workflow_group: DS.belongsTo('panda/workflow-group') + project: DS.belongsTo('panda/project', inverse: 'workflow_action_definitions') + organization: DS.belongsTo('panda/organization', inverse: 'workflow_action_definitions') + workflow_action_constraints: DS.hasMany('panda/workflow-action-constraint', inverse: 'workflow_action_definition') + workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', inverse: 'workflow_action_definition') + dependent_workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', inverse: 'base_workflow_action_definition') + #project_scenarios: DS.hasMany('panda/project-scenario', inverse: 'workflow_action_definitions') + project_scenarios: DS.hasMany('panda/project-scenario', inverse: null) + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'workflow_action_definition') + confirms_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'confirmed_by_workflow_action_definition') + confirmed_by_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'confirms_workflow_action_definition') + + workflow_action_connections: DS.hasMany('panda/workflow-action-connection', inverse: 'workflow_action_definition') + pointing_workflow_action_connections: DS.hasMany('panda/workflow-action-connection', inverse: 'base_workflow_action_definition') + + absolute_position: Ember.computed 'workflow_group.position', 'position', -> + (@get('workflow_group.position') || 99) * 100 + (@get('position') || 99 ) + + #drag_identifier: Ember.computed 'id', 'workflow_group.id', 'position', -> + # "#{@get('id')}-#{@get('workflow_group.id')}-#{@get('position')}" + #smart_position: Ember.computed 'done', 'position', -> + # num = @get('position') + # if @get('done') then num + 1000 else num +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/addon/models/panda/workflow-action-definition.js b/addon/models/panda/workflow-action-definition.js new file mode 100644 index 0000000..d6f1bbc --- /dev/null +++ b/addon/models/panda/workflow-action-definition.js @@ -0,0 +1,91 @@ +// Generated by CoffeeScript 1.12.5 +var Model; + +import Ember from 'ember'; + +import DS from 'ember-data'; + +import add_business_day_computations from 'ember-panda/utils/business-day-computations'; + +export var value_type_options = ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp']; + +Model = { + identifier: DS.attr('string'), + title: DS.attr('string'), + name: Ember.computed.alias('title'), + description: DS.attr('string'), + isEditing: DS.attr('boolean', { + defaultValue: false + }), + done: DS.attr('boolean', { + defaultValue: false + }), + position: DS.attr('number', { + defaultValue: 0 + }), + value_type: DS.attr('string', { + defaultValue: 'boolean' + }), + value_type_select_options: DS.attr({ + defaultValue: function() { + return []; + } + }), + extra_info: DS.attr('string'), + offset_type: DS.attr('string', { + defaultValue: 'none' + }), + overdue_coulance: DS.attr('number', { + defaultValue: 0 + }), + completes_group: DS.attr('boolean', { + defaultValue: false + }), + workflow_group: DS.belongsTo('panda/workflow-group'), + project: DS.belongsTo('panda/project', { + inverse: 'workflow_action_definitions' + }), + organization: DS.belongsTo('panda/organization', { + inverse: 'workflow_action_definitions' + }), + workflow_action_constraints: DS.hasMany('panda/workflow-action-constraint', { + inverse: 'workflow_action_definition' + }), + workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', { + inverse: 'workflow_action_definition' + }), + dependent_workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', { + inverse: 'base_workflow_action_definition' + }), + project_scenarios: DS.hasMany('panda/project-scenario', { + inverse: null + }), + project_instance_actions: DS.hasMany('panda/project-instance-action', { + inverse: 'workflow_action_definition' + }), + confirms_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'confirmed_by_workflow_action_definition' + }), + confirmed_by_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'confirms_workflow_action_definition' + }), + workflow_action_connections: DS.hasMany('panda/workflow-action-connection', { + inverse: 'workflow_action_definition' + }), + pointing_workflow_action_connections: DS.hasMany('panda/workflow-action-connection', { + inverse: 'base_workflow_action_definition' + }), + absolute_position: Ember.computed('workflow_group.position', 'position', function() { + return (this.get('workflow_group.position') || 99) * 100 + (this.get('position') || 99); + }) +}; + +add_business_day_computations(Model, 'offset', { + add_attributes: true +}); + +add_business_day_computations(Model, 'batch_element_threshold', { + add_attributes: true +}); + +export default DS.Model.extend(Model); diff --git a/addon/routes/action-list.coffee b/addon/routes/action-list.coffee new file mode 100644 index 0000000..e8ebaac --- /dev/null +++ b/addon/routes/action-list.coffee @@ -0,0 +1,67 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + filter_calls: 0 + workflow_action_definition_id_in: [] + project_instance_group_workflow_group_id: '' + project_id: '' + project_instance_project_scenario_id: '' + workflow_action_definition_organization_id: '' + offset_type_not_eq: 'attached' + state_in: [] + target_date_gteq: null + target_date_lteq: null + s: 'target_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + @_super arguments... + + model: (params) -> + #@store.findAll('epots/migration-window-planning') + otherParams = {q: default_filter.toProperties()} + includes = [ + 'workflow_action_definition.~workflow_action_connections' + 'workflow_action_definition.~pointing_workflow_action_connections' + 'workflow_action_definition.~organization' + 'workflow_action_definition.~workflow_action_offsets' + 'project_instance_group.~project' + 'project_instance_group.~project_instance' + 'project_instance_group.~project_instance_actions' + 'project_instance_group.workflow_group' + 'project_instance.~project' + 'project_instance.project_scenario.~project' + 'project_instance.project_scenario.~workflow_groups' + 'project_instance.project_scenario.~workflow_action_definitions' + 'pointing_project_instance_action_connections.project_instance_action.~project' + 'pointing_project_instance_action_connections.project_instance_action.~project_instance' + 'pointing_project_instance_action_connections.project_instance_action.~project_instance_group' + 'pointing_project_instance_action_connections.project_instance_action.~pointing_project_instance_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~pointing_workflow_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~organization' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_offsets' + ].join(',') + #includes += ',' + #includes += [ + # 'project.users' + #].join(',') + includes = 'action-list' + otherParams.include = includes + Ember.RSVP.hash + projects: @store.findAll('panda/project') + organizations: @store.findAll('panda/organization') + project_instance_actions: + PagedRemoteArray.create + modelName: 'panda/project-instance-action' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' diff --git a/addon/routes/action-list.js b/addon/routes/action-list.js new file mode 100644 index 0000000..aec9bf6 --- /dev/null +++ b/addon/routes/action-list.js @@ -0,0 +1,54 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array'; + +export var default_filter = Ember.Object.create({ + filter_calls: 0, + workflow_action_definition_id_in: [], + project_instance_group_workflow_group_id: '', + project_id: '', + project_instance_project_scenario_id: '', + workflow_action_definition_organization_id: '', + offset_type_not_eq: 'attached', + state_in: [], + target_date_gteq: null, + target_date_lteq: null, + s: 'target_date asc' +}); + +export default Ember.Route.extend({ + queryParams: { + q: { + refreshModel: true + } + }, + setupController: function(controller, model) { + controller.set('filters', default_filter); + return this._super.apply(this, arguments); + }, + model: function(params) { + var includes, otherParams; + otherParams = { + q: default_filter.toProperties() + }; + includes = ['workflow_action_definition.~workflow_action_connections', 'workflow_action_definition.~pointing_workflow_action_connections', 'workflow_action_definition.~organization', 'workflow_action_definition.~workflow_action_offsets', 'project_instance_group.~project', 'project_instance_group.~project_instance', 'project_instance_group.~project_instance_actions', 'project_instance_group.workflow_group', 'project_instance.~project', 'project_instance.project_scenario.~project', 'project_instance.project_scenario.~workflow_groups', 'project_instance.project_scenario.~workflow_action_definitions', 'pointing_project_instance_action_connections.project_instance_action.~project', 'pointing_project_instance_action_connections.project_instance_action.~project_instance', 'pointing_project_instance_action_connections.project_instance_action.~project_instance_group', 'pointing_project_instance_action_connections.project_instance_action.~pointing_project_instance_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~pointing_workflow_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~organization', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_offsets'].join(','); + includes = 'action-list'; + otherParams.include = includes; + return Ember.RSVP.hash({ + projects: this.store.findAll('panda/project'), + organizations: this.store.findAll('panda/organization'), + project_instance_actions: PagedRemoteArray.create({ + modelName: 'panda/project-instance-action', + store: this.store, + page: params.page || 1, + perPage: params.perPage || 30, + otherParams: otherParams, + paramMapping: { + total_pages: 'total-pages', + total_count: 'total-count' + } + }) + }); + } +}); diff --git a/addon/styles/addon.scss b/addon/styles/addon.scss new file mode 100644 index 0000000..36f1a31 --- /dev/null +++ b/addon/styles/addon.scss @@ -0,0 +1,45 @@ +$sort-background-color: #566573; + +$button-tny: 2px; +$primary-color: blue; +$success-color: green; +$warning-color: yellow; +$alert-color: red; +$secondary-color: grey; +$info-color: lightblue; +$non-default-attention-color: #f2711c; + +@import './semantic-ui-additions'; + +//@import './mixins/animations'; +//@import './mixins/buttons'; + +@import './components/buttons'; +@import './components/format-json'; +@import './components/forms'; +//@import './components/flash-message'; +//@import './components/modal'; +@import './components/multiselect'; +@import './components/project-instance-action-value-display'; +//@import './components/page-title'; +@import './components/resources'; +@import './components/sortable'; +@import './components/select-duration'; +@import './components/state-badge'; +@import './components/tables'; +@import './components/title-box'; + +//@import './resources/login-form'; +@import './resources/businessdays'; +@import './resources/projects'; +@import './resources/project-instance'; +@import './resources/tracker-subject'; +@import './resources/workflow-action-definition'; +@import './resources/workflow-groups'; +@import './resources/workflow-group-constraints'; +@import './resources/manual'; + +// DIAGRAMS ARE SVGs +@import './diagrams/project/workflow-action-offsets-graph'; +@import './diagrams/project/workflow-group-offset-diagram'; +@import './diagrams/project/action-execution-offset-diagram'; diff --git a/addon/styles/components/_buttons.sass b/addon/styles/components/_buttons.sass new file mode 100644 index 0000000..474822e --- /dev/null +++ b/addon/styles/components/_buttons.sass @@ -0,0 +1,50 @@ +//=push-action-button($color: $primary-color) +// +button($bg: $color, $padding: $button-tny) +// margin-bottom: 0 +// &.right +// margin-left: 8px + +//.push-action +// $push-action-padding: $button-tny +// +push-action-button +// .icon +// @extend .fa +// &.new +// +push-action-button($primary-color) +// .icon +// @extend .fa-plus +// &.edit +// +push-action-button($warning-color) +// .icon +// @extend .fa-pencil +// &.destroy +// +push-action-button($alert-color) +// .icon +// @extend .fa-trash +// &.save +// +push-action-button($success-color) +// .icon +// @extend .fa-save +// &.rollback +// +push-action-button($secondary-color) +// .icon +// @extend .fa-undo +// &.show +// +push-action-button($info-color) +// .icon +// @extend .fa-eye +// &.sort +// +push-action-button($sort-background-color) +// .icon +// @extend .fa-sort + +// &.new, &.edit, &.save, &.rollback, &.destroy, &.show, &.info +// margin-right: rem-calc(20) +// &:last-of-type +// margin-right: 0 +// .push-action-content +// padding-left: rem-calc(10) + +.workflow-instance-workflow-group-title, .workflow-action-definition-item, .project-header + .push-action + margin: 0 diff --git a/addon/styles/components/_format-json.sass b/addon/styles/components/_format-json.sass new file mode 100644 index 0000000..9c2b4ab --- /dev/null +++ b/addon/styles/components/_format-json.sass @@ -0,0 +1,8 @@ +.format-json-container + max-height: 500px + overflow: scroll + .json-formatter-top-node-name + cursor: pointer + .json-formatter-constructor-name + // Do not show the names Object and Array + display: none diff --git a/addon/styles/components/_forms.sass b/addon/styles/components/_forms.sass new file mode 100644 index 0000000..8dd0285 --- /dev/null +++ b/addon/styles/components/_forms.sass @@ -0,0 +1,42 @@ +.ui.input + input[type=number] + text-align: right +//.form-row +// +grid-row +// .form-label +// @media #{$small-only} +// +grid-column($columns:10, $center:true) +// @media #{$medium-only} +// +grid-column($columns:4, $offset:1) +// @media #{$large-up} +// +grid-column(3) +// &.half +// +grid-column(6) +// .form-field +// @media #{$small-only} +// +grid-column($columns:10, $center:true, $last-column:true) +// @media #{$medium-only} +// +grid-column($columns:4, $last-column:false) +// @media #{$large-up} +// +grid-column($columns: 4, $last-column:false) +// &.full +// @media #{$small-only} +// +grid-column($columns:10, $center:true, $last-column:true) +// @media #{$medium-only} +// +grid-column($columns:7, $last-column:true) +// @media #{$large-up} +// +grid-column($columns: 9, $last-column:true) +// &.half +// +grid-column(6) +// .error +// color: $alert-color +// .form-info +// @media #{$small-only} +// +grid-column($columns:10, $center:true) +// @media #{$medium-only} +// +grid-column($columns:4, $offset:1) +// @media #{$large-up} +// +grid-column(3) +// .error-message +// color: $alert-color + diff --git a/addon/styles/components/_multiselect.sass b/addon/styles/components/_multiselect.sass new file mode 100644 index 0000000..c118038 --- /dev/null +++ b/addon/styles/components/_multiselect.sass @@ -0,0 +1,2 @@ +.multiselect-checkboxes + list-style: none diff --git a/addon/styles/components/_project-instance-action-value-display.sass b/addon/styles/components/_project-instance-action-value-display.sass new file mode 100644 index 0000000..6a92028 --- /dev/null +++ b/addon/styles/components/_project-instance-action-value-display.sass @@ -0,0 +1,3 @@ +.project-instance-action-value-display + .value-missing + color: #aaa diff --git a/addon/styles/components/_resources.sass b/addon/styles/components/_resources.sass new file mode 100644 index 0000000..0cbf2ee --- /dev/null +++ b/addon/styles/components/_resources.sass @@ -0,0 +1,13 @@ +.resource-row + margin-bottom: rem-calc(10) +h2, .content + .resource-descriptor + padding-right: 8px + &:after + content: ':' + .resource-name + text-decoration: underline + .resource-suffix + font-weight: normal + padding-left: 8px + font-size: 0.8em diff --git a/addon/styles/components/_select-duration.sass b/addon/styles/components/_select-duration.sass new file mode 100644 index 0000000..f85676e --- /dev/null +++ b/addon/styles/components/_select-duration.sass @@ -0,0 +1,6 @@ +.select-duration + .ui.input + &.left.labeled + color: green + &+ .dropdown + margin-left: 51px diff --git a/addon/styles/components/_sortable.sass b/addon/styles/components/_sortable.sass new file mode 100644 index 0000000..af42736 --- /dev/null +++ b/addon/styles/components/_sortable.sass @@ -0,0 +1,2 @@ +.sortable-list + list-style: none diff --git a/addon/styles/components/_state-badge.sass b/addon/styles/components/_state-badge.sass new file mode 100644 index 0000000..5d216ca --- /dev/null +++ b/addon/styles/components/_state-badge.sass @@ -0,0 +1,3 @@ +.state-badge + &.with-action + cursor: pointer diff --git a/addon/styles/components/_tables.sass b/addon/styles/components/_tables.sass new file mode 100644 index 0000000..ec26318 --- /dev/null +++ b/addon/styles/components/_tables.sass @@ -0,0 +1,9 @@ +tr.pointing-record + + .record-with-pointing-records + td + border-top: 3px solid #aaa + td + &:first-child + border-left: 8px solid #aaa + &:last-child + border-right: 8px solid #aaa diff --git a/addon/styles/components/_title-box.sass b/addon/styles/components/_title-box.sass new file mode 100644 index 0000000..5cd5222 --- /dev/null +++ b/addon/styles/components/_title-box.sass @@ -0,0 +1,71 @@ +/* Based on the bootstrap 3+ panel, of course under a different name. + The conversion is done based on: + http://stackoverflow.com/questions/19220027/can-you-use-panels-in-bootstrap-2-3 +.title-box + padding: 15px + margin-bottom: 20px + background-color: #ffffff + border: 1px solid #dddddd + border-radius: 4px + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) + +.title-box-heading + padding: 10px 15px + margin: -15px -15px 15px + font-size: 17.5px + font-weight: 500 + background-color: #f5f5f5 + border-bottom: 1px solid #dddddd + border-top-right-radius: 3px + border-top-left-radius: 3px + +.title-box-footer + padding: 10px 15px + margin: 15px -15px -15px + background-color: #f5f5f5 + border-top: 1px solid #dddddd + border-bottom-right-radius: 3px + border-bottom-left-radius: 3px + +.title-box-primary + border-color: #428bca + .title-box-heading + color: #ffffff + background-color: #428bca + border-color: #428bca + +.title-box-success + border-color: #d6e9c6 + + .title-box-heading + color: #468847 + background-color: #dff0d8 + border-color: #d6e9c6 + +.title-box-warning + border-color: #fbeed5 + + .title-box-heading + color: #c09853 + background-color: #fcf8e3 + border-color: #fbeed5 + +.title-box-danger + border-color: #eed3d7 + + .title-box-heading + color: #b94a48 + background-color: #f2dede + border-color: #eed3d7 + +.title-box-info + border-color: #bce8f1 + + .title-box-heading + color: #3a87ad + background-color: #d9edf7 + border-color: #bce8f1 + +//State specific additions + diff --git a/addon/styles/diagrams/project/_action-execution-offset-diagram.sass b/addon/styles/diagrams/project/_action-execution-offset-diagram.sass new file mode 100644 index 0000000..b36abbc --- /dev/null +++ b/addon/styles/diagrams/project/_action-execution-offset-diagram.sass @@ -0,0 +1,19 @@ +.action-execution-offset-diagram-component, .action-execution-offset-diagram-component-old2 + .noUi-tooltip + white-space: nowrap + .range-slider-container + padding: 18px 30px +.action-execution-offset-diagram-container + .offset-line + stroke: #adadad + stroke-width: 1px + .base-line + stroke: #2185d0 + stroke-width: 3px + .action-descriptions + .action-description-text + text-anchor: start +// within .tooltipstered +.tooltipster-base + .edit-workflow-action-definition + margin: -3px 0 5px 10px !important diff --git a/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass b/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass new file mode 100644 index 0000000..4d63ca4 --- /dev/null +++ b/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass @@ -0,0 +1,3 @@ +.workflow-action-offsets-graph-component + .range-slider-container + padding: 18px 30px diff --git a/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass b/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass new file mode 100644 index 0000000..979b4a9 --- /dev/null +++ b/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass @@ -0,0 +1,31 @@ +.workflow-group-offset-diagram-component + .noUi-tooltip + white-space: nowrap + .range-slider-container + padding: 18px 30px + +// SVG +.workflow-group-offset-diagram-container + .groups-crossing-left-axis + text + fill: black + font-weight: bold + .groups-crossing-right-axis + text + fill: black + font-weight: bold + text-anchor: end + .bar + fill: #5f89ad + &.lead-time-none + fill: #f2711c + &.extra-duration + fill: #fff + fill-opacity: 0.2 + .offset-line + stroke: #adadad + stroke-width: 1px + .base-line + stroke: #2185d0 + stroke-width: 3px + diff --git a/addon/styles/resources/_businessdays.sass b/addon/styles/resources/_businessdays.sass new file mode 100644 index 0000000..a415180 --- /dev/null +++ b/addon/styles/resources/_businessdays.sass @@ -0,0 +1,6 @@ +.default-business-days +.non-default-business-days + .select-businessdays-in-week + color: $non-default-attention-color + .ui.header + color: $non-default-attention-color diff --git a/addon/styles/resources/_login-form.sass b/addon/styles/resources/_login-form.sass new file mode 100644 index 0000000..390c0fb --- /dev/null +++ b/addon/styles/resources/_login-form.sass @@ -0,0 +1,5 @@ +.login-form + &.ui.grid + margin-top: 20px + .column + max-width: 450px diff --git a/addon/styles/resources/_manual.sass b/addon/styles/resources/_manual.sass new file mode 100644 index 0000000..5bcacf3 --- /dev/null +++ b/addon/styles/resources/_manual.sass @@ -0,0 +1,7 @@ +.ui.grid.manual + .nav-column + padding-top: 2rem + padding-left: 2rem + .ui.sticky.fixed.top + // allow space for top navigation + padding-top: 50px diff --git a/addon/styles/resources/_project-instance.sass b/addon/styles/resources/_project-instance.sass new file mode 100644 index 0000000..da2f418 --- /dev/null +++ b/addon/styles/resources/_project-instance.sass @@ -0,0 +1,50 @@ +.project-instance-list-tab-container + .project-instance-list-workflow-group-container + display: table + border-bottom: 1px solid #aaa + width: 100% + &:first-child + border-top: 1px solid #aaa + .project-instance-list-workflow-group-row + display: table-row + font-size: 1.2em + font-weight: bold + background-color: #ddd + .project-instance-list-workflow-action-definition-row + display: table-row + .project-instance-list-workflow-group, .project-instance-list-workflow-action-definition + display: table-cell + width: 33% + padding: 7px + .project-instance-list-workflow-step-context, .project-instance-list-workflow-action-context + display: table-cell + padding: 7px + //.project-instance-list-workflow-group + //font-size: 1.2em + //font-weight: bold + //background-color: #ddd + //.project-instance-list-workflow-step-context + //line-height: 1.2em + //padding: 7px + //background-color: #dff + //border-top: 1px solid #bbb + //&:last-child + //border-bottom: 1px solid #bbb + //.project-instance-list-workflow-action-definition + //border-bottom: 1px solid #bbb + //&:first-child + //border-top: 1px solid #bbb + //.project-instance-list-workflow-action-context + //border-top: 1px solid #bbb + //&:last-child + //border-bottom: 1px solid #bbb +table.ui.definition.table + tr.pointing-action + &.constraint + &.uncompleted + td + font-weight: bold + color: #700 + i + font-size: 1.5em + color: #700 diff --git a/addon/styles/resources/_projects.sass b/addon/styles/resources/_projects.sass new file mode 100644 index 0000000..033afbd --- /dev/null +++ b/addon/styles/resources/_projects.sass @@ -0,0 +1,19 @@ +ul.project-user-select + workflow_group-style: none +.ui.header + .project-scenarios-dropdown-container + float: right + margin-top: -3px // visual + font-size: 0.7em + +.project-scenarios-dropdown-container + .ui.multiple.dropdown + i.dropdown.icon + margin-right: -20px + margin-top: 5px + .ui.button.logical-icon-button + padding: 2px 7px 0px + .logical-icon-image + height: 26px +.active-project-scenario-selector-component.ui.buttons + margin-left: 8px diff --git a/addon/styles/resources/_tracker-subject.sass b/addon/styles/resources/_tracker-subject.sass new file mode 100644 index 0000000..90d88bf --- /dev/null +++ b/addon/styles/resources/_tracker-subject.sass @@ -0,0 +1,34 @@ +.tracker-subject-tracker-groups-container + display: flex + flex-direction: row + flex-wrap: wrap + align-items: flex-start + justify-content: space-around + +.tracker-subject-tracker-group-container + @extend .title-box + //float: left + width: 412px + .tracker-subject-tracker-group-title + @extend .title-box-heading + .tracker-subject-tracker-group-body + .display-row + &.attribute-notes + display: none + .display-label + width: 65% + .display-field + width: 35% + .page-actions + //display: none + text-align: center + .collection-edit + margin: 0 + padding: 5px 12px + &.processing + @extend .title-box-primary + &.rejected + @extend .title-box-danger + &.completed + @extend .title-box-success + diff --git a/addon/styles/resources/_workflow-action-definition.sass b/addon/styles/resources/_workflow-action-definition.sass new file mode 100644 index 0000000..9ffffb9 --- /dev/null +++ b/addon/styles/resources/_workflow-action-definition.sass @@ -0,0 +1,50 @@ +.workflow-action-definition-item + color: #222 + &.is-editing + background-color: #FEF9E7 + &.done + text-decoration: line-through + color: #aaa + &:hover + color: white + .title + text-decoration: line-through + input + margin: 0 +//.workflow-action-definition-value-type-display +// @extend .fa, .fa-lg +// &.boolean +// @extend .checker +// &.date +// @extend .fa-calendar +// &.number +// @extend .fa-hashtag +// &.select +// @extend .fa-list-ul +// &.string +// @extend .fa-terminal +// &.text +// @extend .fa-sticky-note-o +// &.window_from +// @extend .fa-clock-o +// &.window_to +// @extend .fa-clock-o +// &.timestamp +// @extend .fa-clock-o +.ui.dropdown + .workflow-action-definition-value-type-display + padding: 0 + margin-right: 6px + margin-left: -10px +.ui.grid.workflow-action-definition-row + > .column + padding-top: 0.3rem + padding-bottom: 0.3rem + .right.floated.icon.buttons + // normal setting is margin-top: -0.5rem + margin-top: -0.2rem +.workflow-action-definition-workflow-action-offset-container + // below divider, pull a bit up + margin-top: -10px + .number-input + width: 82px diff --git a/addon/styles/resources/_workflow-group-constraints.sass b/addon/styles/resources/_workflow-group-constraints.sass new file mode 100644 index 0000000..a23c2b9 --- /dev/null +++ b/addon/styles/resources/_workflow-group-constraints.sass @@ -0,0 +1,4 @@ +.workflow-group-constraints-count + .count + display: inline-block + margin-right: 8px diff --git a/addon/styles/resources/_workflow-groups.sass b/addon/styles/resources/_workflow-groups.sass new file mode 100644 index 0000000..26fbdee --- /dev/null +++ b/addon/styles/resources/_workflow-groups.sass @@ -0,0 +1,71 @@ +.new-workflow_action_definition-button + display: inline-block + margin-left: 12px + .icon + margin-right: 12px + +.workflow_group-name + padding-right: rem-calc(10) + +.workflow-instance-workflow-groups-container + display: flex + flex-direction: row + flex-wrap: wrap + align-items: flex-start + justify-content: space-around + +.workflow-instance-workflow-group-container + @extend .title-box + //float: left + width: 412px + padding-bottom: 0 + .workflow-instance-workflow-group-title + @extend .title-box-heading + margin-bottom: 0 + .right.floated.buttons + margin-right: -12px + + .workflow-instance-workflow-group-body + .display-row + &.attribute-notes + display: none + .display-label + width: 65% + .display-field + width: 35% + .page-actions + //display: none + text-align: center + .collection-edit + margin: 0 + padding: 5px 12px + .workflow-action-container + border-bottom: 1px solid #eee + margin-left: -15px + margin-right: -15px + &:last-child + border-width: 0 + .ui.grid + margin: 0 + &.planned + background-color: #ddd + &.overdue + background-color: #f7da00 + &.processing + background-color: #cef + &.rejected + background-color: #ff4848 + &.completed + background-color: #f2ffdd + &.processing + @extend .title-box-primary + &.rejected + @extend .title-box-danger + &.completed + @extend .title-box-success + &.overdue + @extend .title-box-warning + +.modal + .workflow-instance-workflow-group-container + width: 242px diff --git a/addon/styles/semantic-ui-additions.sass b/addon/styles/semantic-ui-additions.sass new file mode 100644 index 0000000..b0c3f05 --- /dev/null +++ b/addon/styles/semantic-ui-additions.sass @@ -0,0 +1,29 @@ +//.ui.container +// width: 98% + +.ui.main + padding-top: 40px + +.hide + display: none + + +.right.floated.buttons + margin-top: -0.5rem + +.menu.fixed + .header.item + padding: 0 1rem + background-color: #444 !important + .grid + margin-top: -1rem + //margin-left: 0 # active in epots adapter + +.ui.header + .push-action.icon + padding: 0.6rem + margin-right: 0 + +.ui.segment.title-box + padding-top: 3rem + diff --git a/addon/templates/components/project/edit-active-project-scenario.emblem b/addon/templates/components/project/edit-active-project-scenario.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/addon/templates/components/project/edit-active-project-scenario.emblem @@ -0,0 +1 @@ += yield diff --git a/addon/utils/business-day-computations.coffee b/addon/utils/business-day-computations.coffee new file mode 100644 index 0000000..905ea3c --- /dev/null +++ b/addon/utils/business-day-computations.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +import DS from 'ember-data' +businessDayComputations = (target, prefix, options = {}) -> + if options.add_attributes + Ember.defineProperty target, "#{prefix}_value", DS.attr('number', defaultValue: 0) + Ember.defineProperty target, "#{prefix}_unit", DS.attr('string', defaultValue: options.default_unit || 'day') + Ember.defineProperty target, "#{prefix}_business_days", DS.attr(defaultValue: -> [ 1, 2, 3, 4, 5 ]) + #Ember.defineProperty target, "#{prefix}_business_days_index_sum", Ember.computed.sum "#{prefix}_business_days" + #Ember.defineProperty target, "has_default_#{prefix}_business_days", Ember.computed "#{prefix}_business_days.length", "#{prefix}_business_days_index_sum", -> + # @get("#{prefix}_business_days.length") is 5 and @get("#{prefix}_business_days_index_sum") is 15 + #Ember.defineProperty target, "#{prefix}_unit_businessday", Ember.computed.equal "#{prefix}_unit", 'businessday' + true + +export default businessDayComputations diff --git a/addon/utils/business-day-computations.js b/addon/utils/business-day-computations.js new file mode 100644 index 0000000..fe4645d --- /dev/null +++ b/addon/utils/business-day-computations.js @@ -0,0 +1,28 @@ +// Generated by CoffeeScript 1.12.5 +var businessDayComputations; + +import Ember from 'ember'; + +import DS from 'ember-data'; + +businessDayComputations = function(target, prefix, options) { + if (options == null) { + options = {}; + } + if (options.add_attributes) { + Ember.defineProperty(target, prefix + "_value", DS.attr('number', { + defaultValue: 0 + })); + Ember.defineProperty(target, prefix + "_unit", DS.attr('string', { + defaultValue: options.default_unit || 'day' + })); + Ember.defineProperty(target, prefix + "_business_days", DS.attr({ + defaultValue: function() { + return [1, 2, 3, 4, 5]; + } + })); + } + return true; +}; + +export default businessDayComputations; diff --git a/addon/utils/moment-business-days.coffee b/addon/utils/moment-business-days.coffee new file mode 100644 index 0000000..1dcb787 --- /dev/null +++ b/addon/utils/moment-business-days.coffee @@ -0,0 +1,148 @@ +'use strict' +# taken from https://github.com/kalmecak/moment-business-days +#if (typeof require === 'function') { +# var moment = require('moment'); +#} + +moment.fn.isHoliday = -> + locale = @localeData() + if locale._holidays + if locale._holidays.indexOf(@format(locale._holidayFormat)) >= 0 + return true + if locale.holiday + if locale.holiday(this) + return true + else + return false + false + +moment.fn.isBusinessDay = (options) -> + locale = @localeData() + defaultWorkingWeekdays = [ 1, 2, 3, 4, 5 ] + workingWeekdays = options.business_days or locale._workingWeekdays or defaultWorkingWeekdays + if @isHoliday() + return false + if workingWeekdays.indexOf(@day()) >= 0 + return true + false + +moment.fn.businessDaysIntoMonth = -> + businessDay = if @isBusinessDay() then this else @prevBusinessDay() + monthBusinessDays = businessDay.monthBusinessDays() + businessDaysIntoMonth = undefined + monthBusinessDays.map (day, index) -> + if day.format('M/DD/YY') == businessDay.format('M/DD/YY') + businessDaysIntoMonth = index + 1 + return + businessDaysIntoMonth + +moment.fn.businessDiff = (param) -> + d1 = @clone() + d2 = param.clone() + start = if d1 < d2 then d1 else d2 + end = if d2 > d1 then d2 else d1 + daysBetween = 0 + if start == end + return daysBetween + while start < end + if start.isBusinessDay() + daysBetween++ + start.add 1, 'd' + daysBetween + +moment.fn.businessAdd = (number, options = {}) -> + options.period ||= 'days' + day = @clone() + signal = if number < 0 then -1 else 1 + remaining = Math.abs(number) + while remaining + day.add signal, options.period + if day.isBusinessDay(options) + remaining-- + day + +moment.fn.businessSubtract = (number, options = {}) -> + @businessAdd -number, options + +moment.fn.nextBusinessDay = -> + iterator = 1 + limit = 7 + while iterator < limit + if @add(1, 'd').isBusinessDay() + break + iterator++ + this + +moment.fn.prevBusinessDay = -> + iterator = 1 + limit = 7 + while iterator < limit + if @subtract(1, 'd').isBusinessDay() + break + iterator++ + this + +moment.fn.monthBusinessDays = (partialEndDate) -> + me = @clone() + day = me.clone().startOf('month') + end = if partialEndDate then partialEndDate else me.clone().endOf('month') + daysArr = [] + done = false + while !done + if day.isBusinessDay() + daysArr.push day.clone() + if end.diff(day.add(1, 'd')) < 0 + done = true + daysArr + +moment.fn.monthNaturalDays = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + daysArr = [] + done = false + while !done + daysArr.push day.clone() + if end.diff(day.add(1, 'd')) < 0 + done = true + daysArr + +moment.fn.monthBusinessWeeks = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + weeksArr = [] + daysArr = [] + done = false + while !done + if day.day() >= 1 and day.day() < 6 + daysArr.push day.clone() + if day.day() == 5 + weeksArr.push daysArr + daysArr = [] + if end.diff(day.add(1, 'd')) < 0 + if daysArr.length < 5 + weeksArr.push daysArr + done = true + weeksArr + +moment.fn.monthNaturalWeeks = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + weeksArr = [] + daysArr = [] + done = false + while !done + daysArr.push day.clone() + if day.day() == 6 + weeksArr.push daysArr + daysArr = [] + if end.diff(day.add(1, 'd')) < 0 + if daysArr.length < 7 + weeksArr.push daysArr + done = true + weeksArr + +if typeof module isnt 'undefined' and module.exports + module.exports = moment diff --git a/addon/utils/moment-business-days.js b/addon/utils/moment-business-days.js new file mode 100644 index 0000000..b42bd7c --- /dev/null +++ b/addon/utils/moment-business-days.js @@ -0,0 +1,204 @@ +// Generated by CoffeeScript 1.12.5 +'use strict'; +moment.fn.isHoliday = function() { + var locale; + locale = this.localeData(); + if (locale._holidays) { + if (locale._holidays.indexOf(this.format(locale._holidayFormat)) >= 0) { + return true; + } + } + if (locale.holiday) { + if (locale.holiday(this)) { + return true; + } else { + return false; + } + } + return false; +}; + +moment.fn.isBusinessDay = function(options) { + var defaultWorkingWeekdays, locale, workingWeekdays; + locale = this.localeData(); + defaultWorkingWeekdays = [1, 2, 3, 4, 5]; + workingWeekdays = options.business_days || locale._workingWeekdays || defaultWorkingWeekdays; + if (this.isHoliday()) { + return false; + } + if (workingWeekdays.indexOf(this.day()) >= 0) { + return true; + } + return false; +}; + +moment.fn.businessDaysIntoMonth = function() { + var businessDay, businessDaysIntoMonth, monthBusinessDays; + businessDay = this.isBusinessDay() ? this : this.prevBusinessDay(); + monthBusinessDays = businessDay.monthBusinessDays(); + businessDaysIntoMonth = void 0; + monthBusinessDays.map(function(day, index) { + if (day.format('M/DD/YY') === businessDay.format('M/DD/YY')) { + businessDaysIntoMonth = index + 1; + } + }); + return businessDaysIntoMonth; +}; + +moment.fn.businessDiff = function(param) { + var d1, d2, daysBetween, end, start; + d1 = this.clone(); + d2 = param.clone(); + start = d1 < d2 ? d1 : d2; + end = d2 > d1 ? d2 : d1; + daysBetween = 0; + if (start === end) { + return daysBetween; + } + while (start < end) { + if (start.isBusinessDay()) { + daysBetween++; + } + start.add(1, 'd'); + } + return daysBetween; +}; + +moment.fn.businessAdd = function(number, options) { + var day, remaining, signal; + if (options == null) { + options = {}; + } + options.period || (options.period = 'days'); + day = this.clone(); + signal = number < 0 ? -1 : 1; + remaining = Math.abs(number); + while (remaining) { + day.add(signal, options.period); + if (day.isBusinessDay(options)) { + remaining--; + } + } + return day; +}; + +moment.fn.businessSubtract = function(number, options) { + if (options == null) { + options = {}; + } + return this.businessAdd(-number, options); +}; + +moment.fn.nextBusinessDay = function() { + var iterator, limit; + iterator = 1; + limit = 7; + while (iterator < limit) { + if (this.add(1, 'd').isBusinessDay()) { + break; + } + iterator++; + } + return this; +}; + +moment.fn.prevBusinessDay = function() { + var iterator, limit; + iterator = 1; + limit = 7; + while (iterator < limit) { + if (this.subtract(1, 'd').isBusinessDay()) { + break; + } + iterator++; + } + return this; +}; + +moment.fn.monthBusinessDays = function(partialEndDate) { + var day, daysArr, done, end, me; + me = this.clone(); + day = me.clone().startOf('month'); + end = partialEndDate ? partialEndDate : me.clone().endOf('month'); + daysArr = []; + done = false; + while (!done) { + if (day.isBusinessDay()) { + daysArr.push(day.clone()); + } + if (end.diff(day.add(1, 'd')) < 0) { + done = true; + } + } + return daysArr; +}; + +moment.fn.monthNaturalDays = function(fromToday) { + var day, daysArr, done, end, me; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + daysArr = []; + done = false; + while (!done) { + daysArr.push(day.clone()); + if (end.diff(day.add(1, 'd')) < 0) { + done = true; + } + } + return daysArr; +}; + +moment.fn.monthBusinessWeeks = function(fromToday) { + var day, daysArr, done, end, me, weeksArr; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + weeksArr = []; + daysArr = []; + done = false; + while (!done) { + if (day.day() >= 1 && day.day() < 6) { + daysArr.push(day.clone()); + } + if (day.day() === 5) { + weeksArr.push(daysArr); + daysArr = []; + } + if (end.diff(day.add(1, 'd')) < 0) { + if (daysArr.length < 5) { + weeksArr.push(daysArr); + } + done = true; + } + } + return weeksArr; +}; + +moment.fn.monthNaturalWeeks = function(fromToday) { + var day, daysArr, done, end, me, weeksArr; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + weeksArr = []; + daysArr = []; + done = false; + while (!done) { + daysArr.push(day.clone()); + if (day.day() === 6) { + weeksArr.push(daysArr); + daysArr = []; + } + if (end.diff(day.add(1, 'd')) < 0) { + if (daysArr.length < 7) { + weeksArr.push(daysArr); + } + done = true; + } + } + return weeksArr; +}; + +if (typeof module !== 'undefined' && module.exports) { + module.exports = moment; +} diff --git a/addon/utils/panda-route-setup.coffee b/addon/utils/panda-route-setup.coffee new file mode 100644 index 0000000..0e6db58 --- /dev/null +++ b/addon/utils/panda-route-setup.coffee @@ -0,0 +1,51 @@ +export default pandaRouteSetup = (context) -> + context.route 'project', -> + @route 'show', path: ':project_id', -> + @route 'dunlop' + @route 'tracker-subjects' + @route 'tracker-subject', path: 'tracker-subject/:tracker_subject_id' + @route 'add-progress' + #@route 'tracker-subject', -> + # @route 'show' + @route 'dunlop-generator' + @route 'analysis' + @route 'project-instance', -> + @route 'show', path: ':project_instance_id', -> + @route 'groups' + @route 'list' + @route 'actions' + @route 'graphs' + @route 'project-instance-group', -> + @route 'show', path: ':project_instance_group_id' + @route 'project-instance-action', -> + @route 'show', path: ':project_instance_action_id' + #@route 'project/views', path: 'views' + context.route 'login' + #@route 'organization', path: 'organizations/:organization_id' + context.route 'action-list' + + context.route 'organization', -> + @route 'show', path: ':organization_id', -> + @route 'project-actions' + + context.route 'user', -> + @route 'profile' + @route 'show', path: ':user_id' + + + context.route 'tracker-subject', -> + @route 'show', path: ':tracker_subject_id', -> + @route 'tracking-connections' + @route 'tracker-group', path: 'tracker-groups/:tracker_group_id' + @route 'project', path: 'projects/:project_id' + #@route 'tracker-subject/tracker-group', path: 'tracker-subjects/:tracker_subject_id/tracker-groups/:tracker_group_id' + context.route 'event' + + context.route 'meeting-reports' + + context.route 'manual', -> + @route 'project', -> + @route 'dunlop' + @route 'tracker', -> + @route 'tricks' # dummy/empty now, to genate the index + true diff --git a/addon/utils/panda-route-setup.js b/addon/utils/panda-route-setup.js new file mode 100644 index 0000000..81bf07b --- /dev/null +++ b/addon/utils/panda-route-setup.js @@ -0,0 +1,78 @@ +// Generated by CoffeeScript 1.12.5 +var pandaRouteSetup; + +export default pandaRouteSetup = function(context) { + context.route('project', function() { + return this.route('show', { + path: ':project_id' + }, function() { + this.route('dunlop'); + this.route('tracker-subjects'); + this.route('tracker-subject', { + path: 'tracker-subject/:tracker_subject_id' + }); + this.route('add-progress'); + this.route('dunlop-generator'); + this.route('analysis'); + this.route('project-instance', function() { + return this.route('show', { + path: ':project_instance_id' + }, function() { + this.route('groups'); + this.route('list'); + this.route('actions'); + return this.route('graphs'); + }); + }); + this.route('project-instance-group', function() { + return this.route('show', { + path: ':project_instance_group_id' + }); + }); + return this.route('project-instance-action', function() { + return this.route('show', { + path: ':project_instance_action_id' + }); + }); + }); + }); + context.route('login'); + context.route('action-list'); + context.route('organization', function() { + return this.route('show', { + path: ':organization_id' + }, function() { + return this.route('project-actions'); + }); + }); + context.route('user', function() { + this.route('profile'); + return this.route('show', { + path: ':user_id' + }); + }); + context.route('tracker-subject', function() { + return this.route('show', { + path: ':tracker_subject_id' + }, function() { + this.route('tracking-connections'); + this.route('tracker-group', { + path: 'tracker-groups/:tracker_group_id' + }); + return this.route('project', { + path: 'projects/:project_id' + }); + }); + }); + context.route('event'); + context.route('meeting-reports'); + context.route('manual', function() { + this.route('project', function() { + return this.route('dunlop'); + }); + return this.route('tracker', function() { + return this.route('tricks'); + }); + }); + return true; +}; diff --git a/addon/utils/project/active-workflow-action-definitions.coffee b/addon/utils/project/active-workflow-action-definitions.coffee new file mode 100644 index 0000000..7a4d85e --- /dev/null +++ b/addon/utils/project/active-workflow-action-definitions.coffee @@ -0,0 +1,45 @@ +import Ember from 'ember' +# get_active_workflow_action_definitions(workflow_action_definitions, project_scenarios, set_operation = 'union or intersection') +# this util takes as first arguments the workflow_action_definitions scope +# and tries to apply filtering based on the given project_scenarios and set_operation + +get_active_workflow_action_definitions_using_union = (project_scenarios) -> + selected_project_scenario_workflow_action_definition_ids = [] + working_workflow_action_definitions = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_action_definitions').forEach (workflow_action_definition) -> + if selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0 + selected_project_scenario_workflow_action_definition_ids.push workflow_action_definition.get('id') + working_workflow_action_definitions.push workflow_action_definition + working_workflow_action_definitions + +get_active_workflow_action_definitions_using_intersection = (workflow_action_definitions, project_scenarios) -> + selected_project_scenario_workflow_action_definition_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids + first_run = false + return + selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter (id) -> id in current_workflow_action_definition_ids + workflow_action_definitions.filter((r) -> r.get('id') in selected_project_scenario_workflow_action_definition_ids) + +get_active_workflow_action_definitions = (workflow_action_definitions, project_scenarios, set_operation = 'union') -> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_action_definitions = workflow_action_definitions.toArray() + # initialize unused working workflow-action-definitions + if project_scenarios.length + if set_operation is 'intersection' + get_active_workflow_action_definitions_using_intersection(workflow_action_definitions, project_scenarios) + else + get_active_workflow_action_definitions_using_union(project_scenarios) + else + # no filtering can be allplied, return all + workflow_action_definitions + +export { get_active_workflow_action_definitions_using_union } +export { get_active_workflow_action_definitions_using_intersection } +export { get_active_workflow_action_definitions } + +export default get_active_workflow_action_definitions diff --git a/addon/utils/project/active-workflow-action-definitions.js b/addon/utils/project/active-workflow-action-definitions.js new file mode 100644 index 0000000..37f8034 --- /dev/null +++ b/addon/utils/project/active-workflow-action-definitions.js @@ -0,0 +1,73 @@ +// Generated by CoffeeScript 1.12.5 +var get_active_workflow_action_definitions, get_active_workflow_action_definitions_using_intersection, get_active_workflow_action_definitions_using_union, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +get_active_workflow_action_definitions_using_union = function(project_scenarios) { + var selected_project_scenario_workflow_action_definition_ids, working_workflow_action_definitions; + selected_project_scenario_workflow_action_definition_ids = []; + working_workflow_action_definitions = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + if (selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0) { + selected_project_scenario_workflow_action_definition_ids.push(workflow_action_definition.get('id')); + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + }); + return working_workflow_action_definitions; +}; + +get_active_workflow_action_definitions_using_intersection = function(workflow_action_definitions, project_scenarios) { + var first_run, selected_project_scenario_workflow_action_definition_ids; + selected_project_scenario_workflow_action_definition_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_action_definition_ids; + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter(function(id) { + return indexOf.call(current_workflow_action_definition_ids, id) >= 0; + }); + }); + return workflow_action_definitions.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_action_definition_ids, ref) >= 0; + }); +}; + +get_active_workflow_action_definitions = function(workflow_action_definitions, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_action_definitions = workflow_action_definitions.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return get_active_workflow_action_definitions_using_intersection(workflow_action_definitions, project_scenarios); + } else { + return get_active_workflow_action_definitions_using_union(project_scenarios); + } + } else { + return workflow_action_definitions; + } +}; + +export { + get_active_workflow_action_definitions_using_union +}; + +export { + get_active_workflow_action_definitions_using_intersection +}; + +export { + get_active_workflow_action_definitions +}; + +export default get_active_workflow_action_definitions; diff --git a/addon/utils/project/active-workflow-groups.coffee b/addon/utils/project/active-workflow-groups.coffee new file mode 100644 index 0000000..2214264 --- /dev/null +++ b/addon/utils/project/active-workflow-groups.coffee @@ -0,0 +1,45 @@ +import Ember from 'ember' +# get_active_workflow_groups(workflow_groups, project_scenarios, set_operation = 'union or intersection') +# this util takes as first arguments the workflow_groups scope +# and tries to apply filtering based on the given project_scenarios and set_operation + +get_active_workflow_groups_using_union = (project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + working_workflow_groups = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_groups').forEach (workflow_group) -> + if selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0 + selected_project_scenario_workflow_group_ids.push workflow_group.get('id') + working_workflow_groups.push workflow_group + working_workflow_groups + +get_active_workflow_groups_using_intersection = (workflow_groups, project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_group_ids = current_workflow_group_ids + first_run = false + return + selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter (id) -> id in current_workflow_group_ids + workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + +get_active_workflow_groups = (workflow_groups, project_scenarios, set_operation = 'union') -> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_groups = workflow_groups.toArray() + # initialize unused working workflow-groups + if project_scenarios.length + if set_operation is 'intersection' + get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios) + else + get_active_workflow_groups_using_union(project_scenarios) + else + # no filtering can be allplied, return all + workflow_groups + +export { get_active_workflow_groups_using_union } +export { get_active_workflow_groups_using_intersection } +export { get_active_workflow_groups } + +export default get_active_workflow_groups diff --git a/addon/utils/project/active-workflow-groups.js b/addon/utils/project/active-workflow-groups.js new file mode 100644 index 0000000..e40bab7 --- /dev/null +++ b/addon/utils/project/active-workflow-groups.js @@ -0,0 +1,73 @@ +// Generated by CoffeeScript 1.12.5 +var get_active_workflow_groups, get_active_workflow_groups_using_intersection, get_active_workflow_groups_using_union, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +get_active_workflow_groups_using_union = function(project_scenarios) { + var selected_project_scenario_workflow_group_ids, working_workflow_groups; + selected_project_scenario_workflow_group_ids = []; + working_workflow_groups = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_groups').forEach(function(workflow_group) { + if (selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0) { + selected_project_scenario_workflow_group_ids.push(workflow_group.get('id')); + return working_workflow_groups.push(workflow_group); + } + }); + }); + return working_workflow_groups; +}; + +get_active_workflow_groups_using_intersection = function(workflow_groups, project_scenarios) { + var first_run, selected_project_scenario_workflow_group_ids; + selected_project_scenario_workflow_group_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_group_ids; + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_group_ids = current_workflow_group_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter(function(id) { + return indexOf.call(current_workflow_group_ids, id) >= 0; + }); + }); + return workflow_groups.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_group_ids, ref) >= 0; + }); +}; + +get_active_workflow_groups = function(workflow_groups, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_groups = workflow_groups.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios); + } else { + return get_active_workflow_groups_using_union(project_scenarios); + } + } else { + return workflow_groups; + } +}; + +export { + get_active_workflow_groups_using_union +}; + +export { + get_active_workflow_groups_using_intersection +}; + +export { + get_active_workflow_groups +}; + +export default get_active_workflow_groups; diff --git a/addon/utils/project/contract.coffee b/addon/utils/project/contract.coffee new file mode 100644 index 0000000..af38167 --- /dev/null +++ b/addon/utils/project/contract.coffee @@ -0,0 +1,4 @@ +projectContract = () -> + true + +export default projectContract diff --git a/addon/utils/project/contract.js b/addon/utils/project/contract.js new file mode 100644 index 0000000..8381e31 --- /dev/null +++ b/addon/utils/project/contract.js @@ -0,0 +1,8 @@ +// Generated by CoffeeScript 1.12.5 +var projectContract; + +projectContract = function() { + return true; +}; + +export default projectContract; diff --git a/addon/utils/project/workflow-action-definition-offset-nodes.coffee b/addon/utils/project/workflow-action-definition-offset-nodes.coffee new file mode 100644 index 0000000..f975347 --- /dev/null +++ b/addon/utils/project/workflow-action-definition-offset-nodes.coffee @@ -0,0 +1,295 @@ +#projectGetWorkflowActionDefinitionOffsets = () -> +# true +#export default projectGetWorkflowActionDefinitionOffsets +import Ember from 'ember' + +RecordNode = Ember.Object.extend + resolved: false + title: Ember.computed 'record.title', 'value_shift', -> + title = @get('record.title') + title = " #{title} (#{value_shift}d)" if value_shift = @get('value_shift') + title + +OffsetNode = Ember.Object.extend + record: null + total_offset: 0 + value_shift: 0 + resulting_absolute_value: 0 + +WorkflowActionDefinitionOffsetNodes = Ember.Object.extend + iteration_context_counter: 0 + workflow_action_definitions: [] + reference_date: null + batch_size: 0 + active_project_scenario: null + + forEach: -> + @get('processed_workflow_action_definitions').forEach(arguments...) + + filterBy: -> + @get('processed_workflow_action_definitions').filterBy(arguments...) + + get_resolved: -> + @get('processed_workflow_action_definitions').filterBy('resolved') + + init: -> + @set 'scope_workflow_action_definition_ids', @get('workflow_action_definitions').mapBy('id').toArray() + @set 'processed_workflow_action_definitions', [] + @set 'working_workflow_action_definitions', [] + @get_workflow_action_definition_offsets() + + get_workflow_action_definition_offsets: -> + base = @ + active_scenario_identifier = @get('active_project_scenario.identifier') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + first_loop_actions = [] + working_workflow_action_definitions = [] + @get('workflow_action_definitions').forEach (workflow_action_definition) -> + action_added = false + if workflow_action_definition.get('offset_type') is 'configured' + workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + return if action_added + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + if not workflow_action_offset.get('base_workflow_action_definition.content') # x_based + [offset_date, offset_value] = base.get_offset_for_target(workflow_action_offset, 0) + [date, value_shift] = base.get_dynamic_offset_for_target(workflow_action_offset, offset_value) + absolute_value = offset_value + value_shift + record_wrapper = RecordNode.create + record: workflow_action_definition + record_id: workflow_action_definition.get('id') + offset_wrappers: [ + OffsetNode.create(total_offset: absolute_value, value_shift: value_shift) + ] + absolute_value: absolute_value + value_shift: value_shift + date: date + resolve_type: 'x' + resolved: true + resolve_level: 0 + first_loop_actions.push record_wrapper + action_added = true + working_workflow_action_definitions.push workflow_action_definition unless action_added + + @set 'processed_workflow_action_definitions', first_loop_actions + @set 'working_workflow_action_definitions', working_workflow_action_definitions + + @incrementProperty 'iteration_context_counter' + #@set 'working_workflow_action_definitions', @get('workflow_action_definitions').filterBy('offset_type', 'configured') + @run_critical_path_resolve_loop() + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (unresolved_workflow_action_definition) => # add unresolved records + offset_wrappers = [] + unresolved_workflow_action_definition.get('workflow_action_offsets').forEach (action_offset) -> + return unless action_offset.get('base_workflow_action_definition.id') in scope_workflow_action_definition_ids # do not add offsets from other scenarios + offset_wrappers.push OffsetNode.create + total_offset: action_offset.get('offset_value') + record: action_offset + @get('processed_workflow_action_definitions').push RecordNode.create + record: unresolved_workflow_action_definition + record_id: unresolved_workflow_action_definition.get('id') + offset_wrappers: offset_wrappers + resolved: false + resolve_level: resolve_level + resolve_type: 'unresolved' + + get_dynamic_offset_for_target: (target, absolute_value) -> + base_date = @get('reference_date').clone().add(absolute_value, 'day') + empty = [base_date, 0] + return empty unless batch_size = @get('batch_size') + return empty unless threshold = target.get('batch_element_threshold_value') + return empty unless threshold_exceedance = Math.floor(batch_size / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold. First day is action day + + period_unit = target.get('batch_element_threshold_unit') + offset_value = target.get('offset_value') + operation = if offset_value < 0 then 'subtract' else 'add' + if period_unit is 'businessday' + operation = "business#{operation.capitalize()}" + shifted_date = base_date.clone()[operation](threshold_exceedance, business_days: target.get('batch_element_threshold_business_days')) + else + shifted_date = base_date.clone()[operation](threshold_exceedance, period_unit) + value_shift = shifted_date.diff(base_date, 'day') + [shifted_date, value_shift] + + get_offset_for_target: (target, absolute_value) -> + base_date = @get('reference_date').clone().add(absolute_value, 'day') + empty = [base_date, 0] + return empty unless offset_value = target.get('offset_value') + period_unit = target.get('offset_unit') + operation = if offset_value < 0 then 'subtract' else 'add' + if period_unit is 'businessday' + operation = "business#{operation.capitalize()}" + shifted_date = base_date.clone()[operation](Math.abs(offset_value), business_days: target.get('offset_business_days')) + else + shifted_date = base_date.clone()[operation](Math.abs(offset_value), period_unit) + value_shift = shifted_date.diff(base_date, 'day') + #debugger if target.get('workflow_action_definition.title') is 'Initial check done' + [shifted_date, value_shift] + + run_critical_path_resolve_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + get_offset_for_target = @get('get_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + return unless working_workflow_action_definition.get('offset_type') is 'configured' + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + all_offsets_resolved = working_workflow_action_definition.get('workflow_action_offsets').every (workflow_action_offset) -> + return true if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('base_workflow_action_definition.id') + if workflow_action_offset.get('base_workflow_action_definition.id') not in scope_workflow_action_definition_ids # base is not present in this scenario, do not pursue this connection + return true + return false + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + true # indicate processed_base_workflow_action_definition_wrapper found for all_offsets_resolved value + + if all_offsets_resolved and resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'critical-path' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() + else + @incrementProperty 'iteration_context_counter' + @run_unresolved_tree_resolve_loop() + + run_unresolved_tree_resolve_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_offset_for_target = @get('get_offset_for_target').bind(@) + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + return unless working_workflow_action_definition.get('offset_type') is 'configured' + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + working_workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + return unless workflow_action_offset.get('base_workflow_action_definition.id') in scope_workflow_action_definition_ids + return unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('base_workflow_action_definition.id') + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + if resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers # .clone()? + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'with-non-rooted-offsets' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() + else + @run_offset_forwarding_loop() + + run_offset_forwarding_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_offset_for_target = @get('get_offset_for_target').bind(@) + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + working_workflow_action_definition.get('dependent_workflow_action_offsets').forEach (workflow_action_offset) -> + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + return unless workflow_action_offset.get('workflow_action_definition.id') in scope_workflow_action_definition_ids + return unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('workflow_action_definition.id') + #return if workflow_action_offset.get('workflow_action_definition.offset_type') is 'none' # do not resolve on none offset typed nodes + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + #NOTE MINUS!!!!!!!!!!!!!!!!!!!!! + current_connection_absolute_value = processed_absolute_value - current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + if resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers # .clone()? + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'offset-lookup-inversion' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() +export default WorkflowActionDefinitionOffsetNodes diff --git a/addon/utils/project/workflow-action-definition-offset-nodes.js b/addon/utils/project/workflow-action-definition-offset-nodes.js new file mode 100644 index 0000000..05e06a4 --- /dev/null +++ b/addon/utils/project/workflow-action-definition-offset-nodes.js @@ -0,0 +1,389 @@ +// Generated by CoffeeScript 1.12.5 +var OffsetNode, RecordNode, WorkflowActionDefinitionOffsetNodes, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +RecordNode = Ember.Object.extend({ + resolved: false, + title: Ember.computed('record.title', 'value_shift', function() { + var title, value_shift; + title = this.get('record.title'); + if (value_shift = this.get('value_shift')) { + title = " " + title + " (" + value_shift + "d)"; + } + return title; + }) +}); + +OffsetNode = Ember.Object.extend({ + record: null, + total_offset: 0, + value_shift: 0, + resulting_absolute_value: 0 +}); + +WorkflowActionDefinitionOffsetNodes = Ember.Object.extend({ + iteration_context_counter: 0, + workflow_action_definitions: [], + reference_date: null, + batch_size: 0, + active_project_scenario: null, + forEach: function() { + var ref; + return (ref = this.get('processed_workflow_action_definitions')).forEach.apply(ref, arguments); + }, + filterBy: function() { + var ref; + return (ref = this.get('processed_workflow_action_definitions')).filterBy.apply(ref, arguments); + }, + get_resolved: function() { + return this.get('processed_workflow_action_definitions').filterBy('resolved'); + }, + init: function() { + this.set('scope_workflow_action_definition_ids', this.get('workflow_action_definitions').mapBy('id').toArray()); + this.set('processed_workflow_action_definitions', []); + this.set('working_workflow_action_definitions', []); + return this.get_workflow_action_definition_offsets(); + }, + get_workflow_action_definition_offsets: function() { + var active_scenario_identifier, base, first_loop_actions, resolve_level, scope_workflow_action_definition_ids, working_workflow_action_definitions; + base = this; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + first_loop_actions = []; + working_workflow_action_definitions = []; + this.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + var action_added; + action_added = false; + if (workflow_action_definition.get('offset_type') === 'configured') { + workflow_action_definition.get('workflow_action_offsets').forEach(function(workflow_action_offset) { + var absolute_value, date, offset_date, offset_value, record_wrapper, ref, ref1, value_shift; + if (action_added) { + return; + } + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (!workflow_action_offset.get('base_workflow_action_definition.content')) { + ref = base.get_offset_for_target(workflow_action_offset, 0), offset_date = ref[0], offset_value = ref[1]; + ref1 = base.get_dynamic_offset_for_target(workflow_action_offset, offset_value), date = ref1[0], value_shift = ref1[1]; + absolute_value = offset_value + value_shift; + record_wrapper = RecordNode.create({ + record: workflow_action_definition, + record_id: workflow_action_definition.get('id'), + offset_wrappers: [ + OffsetNode.create({ + total_offset: absolute_value, + value_shift: value_shift + }) + ], + absolute_value: absolute_value, + value_shift: value_shift, + date: date, + resolve_type: 'x', + resolved: true, + resolve_level: 0 + }); + first_loop_actions.push(record_wrapper); + return action_added = true; + } + }); + } + if (!action_added) { + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + this.set('processed_workflow_action_definitions', first_loop_actions); + this.set('working_workflow_action_definitions', working_workflow_action_definitions); + this.incrementProperty('iteration_context_counter'); + this.run_critical_path_resolve_loop(); + resolve_level = this.get('iteration_context_counter'); + return this.get('working_workflow_action_definitions').forEach((function(_this) { + return function(unresolved_workflow_action_definition) { + var offset_wrappers; + offset_wrappers = []; + unresolved_workflow_action_definition.get('workflow_action_offsets').forEach(function(action_offset) { + var ref; + if (ref = action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + return offset_wrappers.push(OffsetNode.create({ + total_offset: action_offset.get('offset_value'), + record: action_offset + })); + }); + return _this.get('processed_workflow_action_definitions').push(RecordNode.create({ + record: unresolved_workflow_action_definition, + record_id: unresolved_workflow_action_definition.get('id'), + offset_wrappers: offset_wrappers, + resolved: false, + resolve_level: resolve_level, + resolve_type: 'unresolved' + })); + }; + })(this)); + }, + get_dynamic_offset_for_target: function(target, absolute_value) { + var base_date, batch_size, empty, offset_value, operation, period_unit, shifted_date, threshold, threshold_exceedance, value_shift; + base_date = this.get('reference_date').clone().add(absolute_value, 'day'); + empty = [base_date, 0]; + if (!(batch_size = this.get('batch_size'))) { + return empty; + } + if (!(threshold = target.get('batch_element_threshold_value'))) { + return empty; + } + if (!(threshold_exceedance = Math.floor(batch_size / (0.1 + parseInt(threshold))))) { + return empty; + } + period_unit = target.get('batch_element_threshold_unit'); + offset_value = target.get('offset_value'); + operation = offset_value < 0 ? 'subtract' : 'add'; + if (period_unit === 'businessday') { + operation = "business" + (operation.capitalize()); + shifted_date = base_date.clone()[operation](threshold_exceedance, { + business_days: target.get('batch_element_threshold_business_days') + }); + } else { + shifted_date = base_date.clone()[operation](threshold_exceedance, period_unit); + } + value_shift = shifted_date.diff(base_date, 'day'); + return [shifted_date, value_shift]; + }, + get_offset_for_target: function(target, absolute_value) { + var base_date, empty, offset_value, operation, period_unit, shifted_date, value_shift; + base_date = this.get('reference_date').clone().add(absolute_value, 'day'); + empty = [base_date, 0]; + if (!(offset_value = target.get('offset_value'))) { + return empty; + } + period_unit = target.get('offset_unit'); + operation = offset_value < 0 ? 'subtract' : 'add'; + if (period_unit === 'businessday') { + operation = "business" + (operation.capitalize()); + shifted_date = base_date.clone()[operation](Math.abs(offset_value), { + business_days: target.get('offset_business_days') + }); + } else { + shifted_date = base_date.clone()[operation](Math.abs(offset_value), period_unit); + } + value_shift = shifted_date.diff(base_date, 'day'); + return [shifted_date, value_shift]; + }, + run_critical_path_resolve_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var all_offsets_resolved, date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + if (working_workflow_action_definition.get('offset_type') !== 'configured') { + return; + } + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + all_offsets_resolved = working_workflow_action_definition.get('workflow_action_offsets').every(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return true; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('base_workflow_action_definition.id')))) { + if (ref = workflow_action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return true; + } + return false; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + return true; + }); + if (all_offsets_resolved && resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'critical-path' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } else { + this.incrementProperty('iteration_context_counter'); + return this.run_unresolved_tree_resolve_loop(); + } + }, + run_unresolved_tree_resolve_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + if (working_workflow_action_definition.get('offset_type') !== 'configured') { + return; + } + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + working_workflow_action_definition.get('workflow_action_offsets').forEach(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (ref = workflow_action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('base_workflow_action_definition.id')))) { + return; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + return resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + }); + if (resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'with-non-rooted-offsets' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } else { + return this.run_offset_forwarding_loop(); + } + }, + run_offset_forwarding_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + working_workflow_action_definition.get('dependent_workflow_action_offsets').forEach(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (ref = workflow_action_offset.get('workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('workflow_action_definition.id')))) { + return; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value - current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + return resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + }); + if (resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'offset-lookup-inversion' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } + } +}); + +export default WorkflowActionDefinitionOffsetNodes; diff --git a/addon/validations/organization.coffee b/addon/validations/organization.coffee new file mode 100644 index 0000000..73362b4 --- /dev/null +++ b/addon/validations/organization.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + name: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/organization.js b/addon/validations/organization.js new file mode 100644 index 0000000..679e0d9 --- /dev/null +++ b/addon/validations/organization.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + name: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/project-instance.coffee b/addon/validations/project-instance.coffee new file mode 100644 index 0000000..65e9893 --- /dev/null +++ b/addon/validations/project-instance.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + client_identifier: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.project_instance.#{key}"), type: type +export default validations + diff --git a/addon/validations/project-instance.js b/addon/validations/project-instance.js new file mode 100644 index 0000000..d323acb --- /dev/null +++ b/addon/validations/project-instance.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + client_identifier: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.project_instance." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/project.coffee b/addon/validations/project.coffee new file mode 100644 index 0000000..73362b4 --- /dev/null +++ b/addon/validations/project.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + name: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/project.js b/addon/validations/project.js new file mode 100644 index 0000000..679e0d9 --- /dev/null +++ b/addon/validations/project.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + name: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/user.coffee b/addon/validations/user.coffee new file mode 100644 index 0000000..1ba4719 --- /dev/null +++ b/addon/validations/user.coffee @@ -0,0 +1,14 @@ +import { + validatePresence, + validateFormat +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + nickname: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.user.#{key}"), type: type + email: validateFormat type: 'email', message: (key, type) -> + buildMessage t("attributes.user.#{key}"), type: type + +export default validations + diff --git a/addon/validations/user.js b/addon/validations/user.js new file mode 100644 index 0000000..1a4c226 --- /dev/null +++ b/addon/validations/user.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence, + validateFormat +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + nickname: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.user." + key), { + type: type + }); + } + }), + email: validateFormat({ + type: 'email', + message: function(key, type) { + return buildMessage(t("attributes.user." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/workflow-action-definition.coffee b/addon/validations/workflow-action-definition.coffee new file mode 100644 index 0000000..5b9fde8 --- /dev/null +++ b/addon/validations/workflow-action-definition.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + title: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/workflow-action-definition.js b/addon/validations/workflow-action-definition.js new file mode 100644 index 0000000..27e1bfa --- /dev/null +++ b/addon/validations/workflow-action-definition.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + title: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/app/.gitkeep b/app/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/components/action-list/organization-filter.js b/app/components/action-list/organization-filter.js new file mode 100644 index 0000000..26b1472 --- /dev/null +++ b/app/components/action-list/organization-filter.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/action-list/organization-filter'; diff --git a/app/components/action-list/project-filter.js b/app/components/action-list/project-filter.js new file mode 100644 index 0000000..a59ea59 --- /dev/null +++ b/app/components/action-list/project-filter.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/action-list/project-filter'; diff --git a/app/components/format-json.coffee b/app/components/format-json.coffee new file mode 100644 index 0000000..454725f --- /dev/null +++ b/app/components/format-json.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +import JSONFormatter from 'npm:json-formatter-js' + +export default Ember.Component.extend + classNames: ['ui', 'secondary', 'segment', 'format-json-container'] + value: {} + nesting_level: Infinity + top_title: 'JSON' + didInsertElement: -> + formatter = new JSONFormatter.default(@get('value'), @get('nesting_level')) + v = formatter.render() + tree = $(v) + tree.find('.json-formatter-constructor-name:first').removeClass('json-formatter-constructor-name').addClass('json-formatter-top-node-name').text(@get('top_title')) + @$().append(v) diff --git a/app/components/labeled-content-choice.js b/app/components/labeled-content-choice.js new file mode 100644 index 0000000..926b613 --- /dev/null +++ b/app/components/labeled-content-choice.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ +}); diff --git a/app/components/modals/base-disabled.coffee b/app/components/modals/base-disabled.coffee new file mode 100644 index 0000000..8ffccc7 --- /dev/null +++ b/app/components/modals/base-disabled.coffee @@ -0,0 +1,85 @@ +import Ember from 'ember' +export default Ember.Controller.extend + alert_message: "" + modal_options: {} + layoutName: 'modals/layout' + willOpenModal: -> # can be implemented, is fired after loading attributes, before rendering + title: (-> + # return title if directly set by options + return @get('modal_options.title') if @get('modal_options.title') + # return translated title_path if directly set by controller + translation_params = {} + if title_params = @get('modal_options.title_params') + translation_params = title_params + else if model = @get('model') + translation_params = model.toJSON() if model.toJSON + + # return translated title_path if directly set by options + # return translated title_path if directly set by controller + if title_path = @get('modal_options.title_path') || @get('title_path') + return tspan(title_path, translation_params).htmlSafe() + # return translated title_path if directly set by options + #return tspan(@get('modal_options.title_path'), translation_params).htmlSafe() if @get('modal_options.title_path') + # return translated title_path if directly set by controller + #return tspan(@title_path, translation_params).htmlSafe() if @title_path + # infer title path based on controller name App.modals.AddSectionController => add_section + #underscored = `this.constructor.toString().substr(11).replace(/Controller$/, '').underscore()` + underscored = @constructor.toString().replace(/.*controller:/, '').replace(/\W+$/, '').underscore().replace('/', '.') + #underscored = @constructor.toString().replace(/[a-zA-Z_-]+\@controller:modals\//, '').replace(/:$/, '').underscore() # ember cli + # find translated title or humanize the controller name + if convention_translation = ttry("#{underscored}.title", translation_params) + tspan("#{underscored}.title", translation_params).htmlSafe() + else + underscored.replace(/.*\./,'').capitalize().replace(/_/, ' ') + ).property('model.id', 'modal_options.title_path') + body: (-> + @get('modal_options.body') + ).property('modal_options.body') + save_error: (error)-> + switch error.status + when 403 + @set 'alert_message', 'Unauthorized action' + else + @set 'alert_message', 'Something went wrong' + save_success: -> + if save_callback = @get('modal_options.save') + save_callback.apply(@) + @set 'alert_message', '' + @send 'closeModal' + + actions: + close: -> + if close = @get('modal_options.close') + close.apply(@) + unless @preventClose + @set 'alert_message', '' + @send 'closeModal' + closeOnOverlay: -> + @send('close') if @get('modal_options.closeOnOverlay') + false + modalClick: -> + @send('close') if @get('modal_options.closeOnModalClick') + false + ok: -> + if ok = @get('modal_options.ok') + ok.apply(@) + @set 'alert_message', '' + @send 'closeModal' unless @preventClose + confirm: -> @send('ok') + saveRecord: -> + if @get('model.isValid') + @get('model').save().then @save_success.bind(@), @save_error.bind(@) + #@send 'closeModal' unless @preventClose + rollback_and_close: -> + @get('model').rollbackAttributes() + @send 'close' + destroyRecord: -> + my_scope = @ + destroy_callback = @get('modal_options.destroy_callback') + @modal 'confirm', + title_path: @get('modal_options.destroy_text_path') || 'general.destroy.are_you_sure' + model: @get('model') + ok: -> + @get('model').destroyRecord() + destroy_callback.call(my_scope) if destroy_callback + @send 'closeModal' unless @preventClose diff --git a/app/components/modals/display-workflow-action-definitions.coffee b/app/components/modals/display-workflow-action-definitions.coffee new file mode 100644 index 0000000..39ec68c --- /dev/null +++ b/app/components/modals/display-workflow-action-definitions.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + showAll: true + workflow_groups: Ember.computed.alias "model" + showTitles: Ember.computed "model.length", "title", -> + return false if @get("model.length") is 1 and @get('model.firstObject.name') is @get('title') + true + #workflow_action_definitions: Ember.computed 'model.[]', 'showAll', -> + # workflow_action_definitions = @get('model') + # unless @get('showAll') + # workflow_action_definitions = workflow_action_definitions.rejectBy('done') + # workflow_action_definitions.sortBy('workflow_group.name', 'smart_position') diff --git a/app/components/modals/edit-tracker-group.coffee b/app/components/modals/edit-tracker-group.coffee new file mode 100644 index 0000000..ebbc439 --- /dev/null +++ b/app/components/modals/edit-tracker-group.coffee @@ -0,0 +1,4 @@ +import Ember from 'ember' +import BaseController from './base' +export default BaseController.extend + a: 3 diff --git a/app/components/modals/edit-tracker-subject.coffee b/app/components/modals/edit-tracker-subject.coffee new file mode 100644 index 0000000..0c38df2 --- /dev/null +++ b/app/components/modals/edit-tracker-subject.coffee @@ -0,0 +1,18 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + model_projects: Ember.A() + + willOpenModal: -> + @get("model.projects").then (projects)=> + @set "model_projects", projects.toArray() + + actions: + newViewItem: -> + record = @get('model.tracker_groups').createRecord(isEditing: true) + + saveRecord: -> + @set("model.projects", @get("model_projects")) + if @get("model.isNew") + @get("model").save() # do not close modal + else + @_super() diff --git a/app/components/modals/sort-records.coffee b/app/components/modals/sort-records.coffee new file mode 100644 index 0000000..387f15d --- /dev/null +++ b/app/components/modals/sort-records.coffee @@ -0,0 +1,10 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + sorted_records: Ember.computed 'model.@each.position', -> + @get('model').sortBy 'position' + actions: + reorderItems: (records)-> + (records || []).forEach (record, position) -> + if record.get('position') isnt position + record.set 'position', position + record.save() diff --git a/app/components/number-input-with-panda.coffee b/app/components/number-input-with-panda.coffee new file mode 100644 index 0000000..ad85900 --- /dev/null +++ b/app/components/number-input-with-panda.coffee @@ -0,0 +1,23 @@ +import Ember from 'ember' + +# renamed from number-input to number-input-with-panda in favour of the ember-cli-dunlop version +# using number-input numericValue=path.to.value +export default Ember.Component.extend + classNames: ['number-input'] + step: 1 + stringValue: '' + value: null + didReceiveAttrs: -> + value = @get('value') + if value or value is 0 or value is '0' + @set 'stringValue', value.toString() + else + @set 'stringValue', '' + actions: + set_value: (value) -> + @set 'stringValue', value + if value or value is 0 or value is '0' + @set 'value', parseInt(value, 10) + else + @set 'value', null + false diff --git a/app/components/organization/modals/edit-organization.coffee b/app/components/organization/modals/edit-organization.coffee new file mode 100644 index 0000000..0f88d32 --- /dev/null +++ b/app/components/organization/modals/edit-organization.coffee @@ -0,0 +1,13 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + selected_users: [] + possible_users: [] + willOpenModal: -> + @set 'possible_users', @store.peekAll('user') + @get('model.users').then (users)=> + @set 'selected_users', users.toArray() + + actions: + saveRecord: -> + @set('model.users', @get('selected_users')) + @_super() diff --git a/app/components/panda-image.js b/app/components/panda-image.js new file mode 100644 index 0000000..4b42b5d --- /dev/null +++ b/app/components/panda-image.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/panda-image'; diff --git a/app/components/project-workflow-groups-graph.coffee b/app/components/project-workflow-groups-graph.coffee new file mode 100644 index 0000000..96ad7e4 --- /dev/null +++ b/app/components/project-workflow-groups-graph.coffee @@ -0,0 +1,29 @@ +import Ember from 'ember' +import Viz from 'npm:viz.js' # browserify +export default Ember.Component.extend + + #constraints: Ember.computed.alias "workflow_groups.@each.constraints" + #workflow_groups: Ember.computed.alias('project.sorted_workflow_groups') + #digraph_content: Ember.computed "constraints.@each.type", "constraints.@each.constraint_workflow_group.id", "workflow_groups.@each.name", -> + #digraph_content: Ember.computed "project.update_revision", -> + digraph_content: Ember.computed "project.update_revision", "project.workflow_groups.@each.constraints.@each.type", "project.workflow_groups.@each.constraints.@each.workflow_action_definitions.length", -> + txt = "digraph prof {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\"];\n rankdir=TB;\n" + @get('project.sorted_workflow_groups').forEach (workflow_group)-> + workflow_group_id = "q#{workflow_group.id}" # start with a letter + txt += " #{workflow_group_id} [label=\"#{workflow_group.get('name')}\"];\n" + workflow_group.get('constraints').forEach (constraint)-> + constraint_workflow_group_id = "q#{constraint.get('constraint_workflow_group.id')}" # start with a letter + actions_label = "" + switch length = constraint.get('workflow_action_definitions.length') + when 0 then # nothing + when 1 then actions_label = " label=\"#{constraint.get('workflow_action_definitions.firstObject.name')}\"" + else actions_label = " label=\"#{length} actions\"" + if constraint.get('type') is "restrict" + txt += " #{constraint_workflow_group_id} -> #{workflow_group_id} [color=\"#C0392B\" penwidth=2 #{actions_label}];\n" + else + txt += " #{constraint_workflow_group_id} -> #{workflow_group_id} [color=\"#F5B041\" #{actions_label}];\n" + txt += "}" + svg_content: Ember.computed "digraph_content", -> + data = @get('digraph_content') + svg = Viz(data, format: 'svg') + svg.htmlSafe() diff --git a/app/components/project/action-execution-offset-diagram.coffee b/app/components/project/action-execution-offset-diagram.coffee new file mode 100644 index 0000000..e75da88 --- /dev/null +++ b/app/components/project/action-execution-offset-diagram.coffee @@ -0,0 +1,303 @@ +#import Component from 'ember-c3/components/c3-chart' +import Ember from 'ember' +import groupBy from 'ember-group-by' +#asdf +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +{get} = Ember + +GraphWrapper = Ember.Object.extend + records: [] + absolute_value: 0 + title: Ember.computed 'records.length', 'records.firstObject.title', -> + length = @get('records.length') + if length > 1 + optional_text = @get('records').mapBy('title').join(' & ') + if optional_text.length <= 49 + optional_text + else + actions_name = t('models.plural.workflow_action_definition') + "#{length} #{actions_name}" + else + @get('records.firstObject.title') +#GraphRecordWrapper = Ember.Object.extend +# title: Ember.computed 'record.title', 'value_shift', -> +# title = @get('record.title') +# title = " #{title} (#{value_shift}d)" if value_shift = @get('value_shift') +# title + +#GraphOffsetWrapper = Ember.Object.extend {} +export default Ember.Component.extend + classNames: ['action-execution-offset-diagram-component'] + graph_height: 320 + bar_height: 20 + x_axis_space: 60 + left_space: 70 + svg_width: 1200 + window_min: null + window_max: null + + range_start_default: null + range_start: null + range_end: null + range_bounds: {min: -7, max: 7} + range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property() + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise + chosen_absolute_date: null + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + + graph_title: Ember.computed 'window_min', 'window_max', 'range_start', 'range_end', 'chosen_absolute_date', -> + range_min = @days_offset_format @get('range_start') + range_max = @days_offset_format @get('range_end') + window_min = @days_offset_format @get('window_min') + window_max = @days_offset_format @get('window_max') + range_addition = '' + if range_min isnt window_min or range_max isnt window_max + range_addition = "(visible #{range_min} t/m #{range_max})" + base = "Showing #{t('models.workflow_action_definition')} offsets #{range_addition} having total window #{window_min} t/m #{window_max}" + base += " for #{t('attributes.project_instance.reference_date')} #{@days_offset_format(0)}" if @get('chosen_absolute_date') + base + + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return '' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + actions: + range_changed: ([range_start, range_end]) -> + [range_start, range_end] = [Math.round(range_start), Math.round(range_end)] + @setProperties + range_start_default: [range_start, range_end] + range_start: range_start + range_end: range_end + @_setup_graph() # manual setup, do not use observable patter for range_start and range_end + + batch_size_simulation_change: (value) -> + @setProperties + range_start: null + range_end: null + batch_size_simulation_value: Math.round(value) + + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false + + init: -> + @_super arguments... + #@set 'batch_size_simulation_value', 0 + @get('project.batch_size_lower_value') if @get('project.batch_size_lower_value') + @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + + didReceiveAttrs: -> + console.log 'Reload workflow-action-offset-diagram call from didReceiveAttrs' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + #_graph_observer: Ember.observer 'active_workflow_action_definitions.@each.title', 'active_workflow_action_definitions.@each.offset_value', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', 'active_workflow_action_definitions.@each.id', -> + _graph_observer: Ember.observer 'project.update_revision', -> + console.log 'Reload workflow-action-offset-diagram call from observer' + console.log "update_revision: #{@get('project.update_revision')}, range_start: #{@get('range_start')}, range_end: #{@get('range_end')}" + Ember.run.debounce @, @_setup_graph, 150 + + _setup_graph: -> + console.log 'Reload workflow-actions-offset-diagram' + container_selector = '.action-execution-offset-diagram-container' + @$(container_selector).html('') + component = @ + + return unless @get('active_workflow_action_definitions.length') + batch_size_simulation_value = @get('batch_size_simulation_value') || 0 + reference_date_day_of_week = @get('reference_date_day_of_week') + unless reference_date = @get('chosen_absolute_date') + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('active_workflow_action_definitions') + reference_date: reference_date + batch_size: batch_size_simulation_value + resolved_record_nodes = result_nodes.get_resolved() + return unless get resolved_record_nodes, 'length' + wrappers = [] + x_min = -1 # at least include zero + x_max = 0 + batch_size_simulation_value = @get('batch_size_simulation_value') + resolved_record_nodes.forEach (record_wrapper) -> + if wrapper = wrappers.findBy 'absolute_value', record_wrapper.get('absolute_value') + wrapper.get('records').push record_wrapper + else + absolute_value = record_wrapper.get('absolute_value') + x_min = Math.min(x_min, absolute_value) + x_max = Math.max(x_max, absolute_value) + wrappers.push GraphWrapper.create + records: Ember.A([record_wrapper]) + absolute_value: absolute_value + date: record_wrapper.get('date') + @set 'window_min', x_min + @set 'window_max', x_max + + ##data = @get('workflow_action_definition_groups') + ##return unless data.length + #x_min = d3.min(data.mapBy('absolute_value')) + #x_min = -1 if x_min > 0 # add space for execution line + #x_max = d3.max(data.mapBy('absolute_value')) + #x_max = 0 if x_max < 0 + #x_max += 1 # last ending can have duration 1 when empty + + if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max + @set 'range_bounds', min: x_min, max: x_max + @set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts + else if not @get('range_start_default') # uninitialized + @set 'range_start_default', [x_min, x_max] + + @set 'range_start', x_min unless Number.isFinite(@get('range_start')) + @set 'range_end', x_max unless Number.isFinite(@get('range_end')) + graph_min = @get('range_start') + graph_max = @get('range_end') + + graph_range_width = @get('svg_width') - @get('left_space') - 22 + x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width]) + #yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')]) + #y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(workflow_groups.mapBy('name').reverse()) + + #x = d3.scale.linear().domain([x_min, x_max]).range([0, 872]) + y = d3.scale.linear().domain([0, 1]).range([0, @get('graph_height')]) + + x_ticks = wrappers.mapBy 'absolute_value' + #data.forEach (group) -> + # offset = Ember.get(group, 'value') + # x_ticks.push offset unless offset is 0 or x_ticks.indexOf(offset) > -1 + x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending) + displayed_x_ticks = [] + if x_ticks.length + displayed_x_ticks.push x_ticks[0] + latest_pixel_x_for_space = x(x_ticks[0]) + x_ticks.forEach (v) -> + current_pixel_x = x(v) + if current_pixel_x - latest_pixel_x_for_space > 14 + latest_pixel_x_for_space = current_pixel_x + displayed_x_ticks.push v + xAxis = d3.svg.axis() + .orient('bottom') + .scale(x) + .tickSize(1) + .tickValues(displayed_x_ticks) + .tickFormat component.days_offset_format.bind(component) + @$(container_selector).append '' + svg_height = @get('x_axis_space') + @get('graph_height') + canvas = d3.select("#{container_selector} svg") + .attr 'width', '100%' + .attr 'height', svg_height + .attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}" + + lines = canvas.append('g') + .attr('class', 'offset-lines') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('line') + .data(x_ticks) # first element is y-axis + .enter() + .append('line') + .attr( + x1: (d) -> x(d) + y1: 0 + x2: (d) -> x(d) + y2: y(1) + class: 'offset-line' + ) + + offset_text = (record_wrapper) -> + title_text = "#{record_wrapper.get('record.workflow_group.name')} -> #{record_wrapper.get('title')}" + offset_texts = [] + record_wrapper.get('offset_wrappers').forEach (offset_wrapper) -> + given_offset = offset_wrapper.get('total_offset') || 0 + day_text = if Math.abs(given_offset) > 1 then t('date.unit.plural.day') else t('date.unit.day') + offset_spec = if given_offset then "#{given_offset} #{day_text}" else 'direct' + offset_spec += " (#{value_shift}d)" if value_shift = offset_wrapper.get('value_shift') + if offset_base_action_title = offset_wrapper.get('record.base_workflow_action_definition.title') + offset_base_group_title = offset_wrapper.get('record.base_workflow_action_definition.workflow_group.name') + offset_relation_text = "#{offset_spec}: #{offset_base_group_title} -> #{offset_base_action_title}" + else + offset_relation_text = "#{offset_spec} from X" + offset_texts.push "Offset: #{offset_relation_text}" + "#{title_text}
#{offset_texts.join('
')}" + texts = canvas.append('g') + .attr('class', 'action-descriptions') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('text') + .data(wrappers.filter((d) -> (d.get('absolute_value') >= graph_min) and (d.get('absolute_value') <= graph_max))) + .enter() + .append('text') + .attr 'class', 'action-description-text' + .attr('transform', 'rotate(-90)') + .html (wrapper) -> + wrapper.get('title') + #.html (group) -> + # if group.items.length <= 2 + # group.items.map( (record) -> + # "#{Ember.get(record, 'title')}" + # ).join(', ') + # else + # "#{group.items.length} #{actions_name}" + .attr + x: 10 - @get('graph_height') + y: (d) -> x(Ember.get(d, 'absolute_value')) + dy: -5 + dx: -5 + .on 'click', (wrapper) -> + target = $(@) + unless target.hasClass 'tooltipstered' + target.tooltipster + content: 'Loading...' + contentAsHTML: true + selfDestruction: false + trigger: 'click' + interactive: true + functionReady: -> + $('.tooltipster-base .edit-workflow-action-definition').on 'click', -> + return target.tooltipster('close') unless id = $(@).data('workflowActionDefinition') + return target.tooltipster('close') unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', id) + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + target.tooltipster('close') + + record_offset_texts = wrapper.records.sortBy('record.absolute_position').map(offset_text) + if wrapper.records.length > 1 + target.tooltipster 'content', record_offset_texts.join('
') + else + target.tooltipster 'content', record_offset_texts[0] + target.tooltipster 'open' + + if graph_min <= 0 <= graph_max + base_line = canvas.append('line').attr + x1: x(0) + y1: 0 + x2: x(0) + y2: 15 + @get('graph_height') + class: 'base-line' + transform: "translate(#{@get('left_space')},0)" + + x_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})") + .attr('class','x-axis') + .call(xAxis) + .selectAll('text') + .style('text-anchor', 'end') + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', "rotate(-40)") + + canvas.selectAll('.action-description-action-part').on 'click', (d) -> + return unless workflow_action_definition_id = $(@).data('recordId') + return unless workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', workflow_action_definition_id) + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: workflow_action_definition diff --git a/app/components/project/edit-active-project-scenario.js b/app/components/project/edit-active-project-scenario.js new file mode 100644 index 0000000..2a88ab2 --- /dev/null +++ b/app/components/project/edit-active-project-scenario.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/project/edit-active-project-scenario'; diff --git a/app/components/project/instance-action-value-display.js b/app/components/project/instance-action-value-display.js new file mode 100644 index 0000000..f50cd8b --- /dev/null +++ b/app/components/project/instance-action-value-display.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/project/instance-action-value-display'; diff --git a/app/components/project/modals/contract.coffee b/app/components/project/modals/contract.coffee new file mode 100644 index 0000000..53f0a92 --- /dev/null +++ b/app/components/project/modals/contract.coffee @@ -0,0 +1,88 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import DS from 'ember-data' +add_offset_to = (target, offsets, record, project) -> + Ember.makeArray(offsets).forEach (offset_name) -> + offset_object = + value: record.get("#{offset_name}_value") || 0 + unit: record.get("#{offset_name}_unit") + if offset_object.unit is 'businessday' + offset_object.business_days = record.get("#{offset_name}_business_days") || [1,2,3,4,5] + target[offset_name] = offset_object + target + +export default ModalBase.extend + contract: null + download_file_name: 'PANDA-contract.json' + download_href: Ember.computed 'contract.content', -> + return 'javascript:void(0)' unless content = @get('contract.content') + hrefContent = "data:text/json;charset=utf-8,#{encodeURIComponent(JSON.stringify(content))}" + hrefContent + + willOpenModal: -> + project = @get('model') + component = @ + project_name = String(project.get('name')).replace(/[\W_]/g, '-') + @set 'download_file_name', "PANDA-contract-#{project_name}.json" + @set 'contract', DS.PromiseObject.create + promise: new Ember.RSVP.Promise (resolve) -> + contract = + generating_project: project.get('id') + contract_version: 'v1' + instance_scope: project.get('instance_scope') + workflow_groups: [] + identifier: project.get('identifier') + workflow_actions: [] + scenarios: [] + project.get('sorted_workflow_groups').forEach (workflow_group) -> + workflow_group_object = + name: workflow_group.get('name') + identifier: workflow_group.get('identifier') + offset_type: workflow_group.get('offset_type') + offset_base: workflow_group.get('offset_base.identifier') || null # no undefined + scenarios: workflow_group.get('project_scenarios').mapBy('identifier') + add_offset_to workflow_group_object, ['offset', 'duration', 'batch_element_threshold'], workflow_group + if project.get('instance_scope') is 'batch' + add_offset_to workflow_group_object, 'batch_element_threshold', workflow_group + contract.workflow_groups.push workflow_group_object + project.get('sorted_workflow_action_definitions').forEach (workflow_action_definition) -> + workflow_action_definition_object = + identifier: workflow_action_definition.get('identifier') + title: workflow_action_definition.get('title') + extra_info: workflow_action_definition.get('extra_info') + value_type: workflow_action_definition.get('value_type') + value_type_select_options: workflow_action_definition.get('value_type_select_options') + overdue_coulance: workflow_action_definition.get('overdue_coulance') + offset_type: workflow_action_definition.get('offset_type') + workflow_group: workflow_action_definition.get('workflow_group.identifier') || null # no undefined + confirms_workflow_action: workflow_action_definition.get('confirms_workflow_action_definition.identifier') || null # no undefined + completes_group: workflow_action_definition.get('completes_group') + organization: workflow_action_definition.get('organization.identifier') + scenarios: workflow_action_definition.get('project_scenarios').mapBy('identifier') + contract.workflow_actions.push workflow_action_definition_object + + workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + workflow_action_definition_object.action_offsets ||= [] + offset_object = {} + if base_identifier = workflow_action_offset.get('base_workflow_action_definition.identifier') + offset_object.base_workflow_action = base_identifier + add_offset_to offset_object, 'offset', workflow_action_offset + if project.get('instance_scope') is 'batch' + add_offset_to offset_object, 'batch_element_threshold', workflow_action_offset + workflow_action_definition_object.action_offsets.push offset_object + + if workflow_action_definition.get('offset_type') is 'attached' + workflow_action_definition_object.attached_to = + identifier: workflow_action_definition.get('workflow_action_connection.base_workflow_action_definition.identifier') + conditional_value: workflow_action_definition.get('workflow_action_connection.conditional_base_value') + + workflow_action_definition.get('workflow_action_constraints').forEach (workflow_action_constraint) -> + workflow_action_definition_object.action_constraints ||= [] + workflow_action_definition_object.action_constraints.push + type: workflow_action_constraint.get('type') + base_workflow_action: workflow_action_constraint.get('constraint_workflow_action_definition.identifier') + project.get('project_scenarios').forEach (project_scenario) -> + project_scenario_object = + identifier: project_scenario.get('identifier') + name: project_scenario.get('name') + contract.scenarios.push project_scenario_object + resolve contract diff --git a/app/components/project/modals/edit-instance-action.coffee b/app/components/project/modals/edit-instance-action.coffee new file mode 100644 index 0000000..682aac1 --- /dev/null +++ b/app/components/project/modals/edit-instance-action.coffee @@ -0,0 +1,112 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +ESC_KEY = 27 +export default ModalBase.extend + title: Ember.computed 'model.workflow_action_definition.title', -> + t 'project.edit_instance_action.title', title: @get('model.workflow_action_definition.title') + value_type: Ember.computed.alias 'model.workflow_action_definition.value_type' + string_value: '' + number_value: null + text_value: '' + new_note_content: '' + new_note_content_is_blank: Ember.computed.not 'new_note_content.length' + has_uncompleted_constraints: false + + can_edit_action: Ember.computed 'model.workflow_action_definition.organization.users.[]', -> + return true if @get('globals.current_user.admin') + return true if @get('model.workflow_action_definition.offset_type') is 'attached' + role_names = @get('globals.current_user.role_names') || [] + return true if 'manage-app-panda' in role_names + return true if 'manage-model-panda/project-instance' in role_names + organization_user_ids = @get('model.workflow_action_definition.organization.users')?.mapBy('id') || [] + return true if @get('globals.current_user.id') in organization_user_ids + false + + _initEscListener: -> + closeOnEscapeKey = (ev) => + @send 'rollback_and_close' if ev.keyCode is ESC_KEY + Ember.$('body').on('keyup.modal-dialog', closeOnEscapeKey) + false + + note_events: Ember.computed 'model.events.@each.event_value', -> + @get('model.events').filterBy('event_key', 'note') + + misc_events: Ember.computed 'model.events.length', -> + @get('model.events').rejectBy('event_key', 'note') + + + + willOpenModal: -> + @_initEscListener() + @get('model.events').reload() + @set 'string_value', @get('model.string_value') + @set 'number_value', @get('model.number_value') + @set 'text_value', @get('model.text_value') + @set 'timestamp_value', @get('model.timestamp_value') + @get('model.pointing_project_instance_action_connections').filterBy('connection_type', 'constraint').some (pointing_connection) => + return @set 'has_uncompleted_constraints', true unless pointing_connection.get('project_instance_action.completed') + false + #@get('pointing_project_instance_action_connections').forEach (pointing_connection) -> + + select_class_names: Ember.computed 'model.string_value', -> + return ['green'] + if @get('model.string_value') then 'green button' else 'button' + + + has_changed_timestamp_value: Ember.computed 'timestamp_value', 'model.timestamp_value', -> + current_timestamp = @get('timestamp_value') + model_timestamp = @get('model.timestamp_value') + return false unless current_timestamp or model_timestamp # both false, nothing changed + return true unless current_timestamp and model_timestamp + # now we have two timestamps, lets compare + current_timestamp.toISOString() isnt model_timestamp.toISOString() + + actions: + set_value: (attribute, value) -> + @set "model.#{attribute}", value + @send 'saveRecord' + + set_string_value: -> + @send 'set_value', 'string_value', @get('string_value') + + set_text_value: -> + @send 'set_value', 'text_value', @get('text_value') + + set_number_value: -> + @send 'set_value', 'number_value', @get('number_value') + set_timestamp_value: -> + @send 'set_value', 'timestamp_value', @get('timestamp_value') + + close: -> + Ember.$('body').off('keyup.modal-dialog') + @_super arguments... + + open_current_modal: -> @modal 'project/edit-instance-action', model: @get('model') + + continue_from_inquiry: -> + record = @get('model') + if target_date = @get('model.target_date') + state = if target_date.format('YYYY-MM-DD') < moment().format('YYYY-MM-DD') then 'overdue' else 'active' + else + state = 'active' + record.set 'state', state + record.save().then -> + record.get('events').reload() + add_new_note: -> + return unless content = @get('new_note_content') + now = moment() + new_note_event = @store.createRecord 'panda/action-event', + client_identifier: @get('model.project_instance.client_identifier') + identifier: @get('model.workflow_action_definition.identifier') + event_key: 'note' + event_value: content + event_timestamp: now + new_note_event.save().then => + @set 'new_note_content', '' + @send 'close' + start_inquiry: -> + record = @get('model') + record.set 'state', 'inquiry' + record.save().then => @send 'add_new_note' + #record.save().then -> + # record.get('events').reload() + diff --git a/app/components/project/modals/edit-project-scenario.coffee b/app/components/project/modals/edit-project-scenario.coffee new file mode 100644 index 0000000..e21f0f6 --- /dev/null +++ b/app/components/project/modals/edit-project-scenario.coffee @@ -0,0 +1,67 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + selected_workflow_action_definition_ids: [] + selected_workflow_group_ids: [] + initial_selected_workflow_action_definition_ids: [] + initial_selected_workflow_group_ids: [] + willOpenModal: -> + current_workflow_action_definition_ids = @get('model.workflow_action_definitions').mapBy('id').toArray() + current_workflow_group_ids = @get('model.workflow_groups').mapBy('id').toArray() + @set 'selected_workflow_action_definition_ids', current_workflow_action_definition_ids + @set 'selected_workflow_group_ids', current_workflow_group_ids + + @set 'initial_workflow_action_definition_ids', current_workflow_action_definition_ids.copy() + @set 'initial_workflow_group_ids', current_workflow_group_ids.copy() + actions: + check_all: -> + @set 'selected_workflow_action_definition_ids', @get('model.project.workflow_action_definitions').mapBy('id').toArray() + @set 'selected_workflow_group_ids', @get('model.project.workflow_groups').mapBy('id').toArray() + check_none: -> + @set 'selected_workflow_action_definition_ids', [] + @set 'selected_workflow_group_ids', [] + change_workflow_group_selection: (workflow_group, checked) -> + if checked + @get('selected_workflow_group_ids').addObject workflow_group.get('id') + @get('selected_workflow_action_definition_ids').addObjects workflow_group.get('workflow_action_definitions').mapBy('id') + else + @get('selected_workflow_group_ids').removeObject workflow_group.get('id') + @get('selected_workflow_action_definition_ids').removeObjects workflow_group.get('workflow_action_definitions').mapBy('id') + change_workflow_action_definition_selection: (workflow_action_definition, checked) -> + if checked + @get('selected_workflow_action_definition_ids').addObject workflow_action_definition.get('id') + #@get('model.workflow_action_definitions').addObject workflow_action_definition + else + @get('selected_workflow_action_definition_ids').removeObject workflow_action_definition.get('id') + #@get('model.workflow_action_definitions').removeObject workflow_action_definition + saveRecord: -> + selected_workflow_action_definition_ids = @get('selected_workflow_action_definition_ids') + selected_workflow_group_ids = @get('selected_workflow_group_ids') + initial_workflow_action_definition_ids = @get('initial_workflow_action_definition_ids') + initial_workflow_group_ids = @get('initial_workflow_group_ids') + + added_workflow_action_definition_ids = selected_workflow_action_definition_ids.filter (id) -> id not in initial_workflow_action_definition_ids + removed_workflow_action_definition_ids = initial_workflow_action_definition_ids.filter (id) -> id not in selected_workflow_action_definition_ids + + added_workflow_group_ids = selected_workflow_group_ids.filter (id) -> id not in initial_workflow_group_ids + removed_workflow_group_ids = initial_workflow_group_ids.filter (id) -> id not in selected_workflow_group_ids + + if added_workflow_action_definition_ids.length + added_workflow_action_definitions = @get('model.project.workflow_action_definitions').filter (record) -> + record.get('id') in added_workflow_action_definition_ids + @get('model.workflow_action_definitions').addObjects added_workflow_action_definitions + if removed_workflow_action_definition_ids.length + removed_workflow_action_definitions = @get('model.project.workflow_action_definitions').filter (record) -> + record.get('id') in removed_workflow_action_definition_ids + @get('model.workflow_action_definitions').removeObjects removed_workflow_action_definitions + + if added_workflow_group_ids.length + added_workflow_groups = @get('model.project.workflow_groups').filter (record) -> + record.get('id') in added_workflow_group_ids + @get('model.workflow_groups').addObjects added_workflow_groups + if removed_workflow_group_ids.length + removed_workflow_groups = @get('model.project.workflow_groups').filter (record) -> + record.get('id') in removed_workflow_group_ids + @get('model.workflow_groups').removeObjects removed_workflow_groups + + @_super arguments... + @incrementProperty 'model.project.update_revision' diff --git a/app/components/project/modals/edit-project.coffee b/app/components/project/modals/edit-project.coffee new file mode 100644 index 0000000..a03d0ab --- /dev/null +++ b/app/components/project/modals/edit-project.coffee @@ -0,0 +1,25 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + project_users: Ember.A() + possible_users: [] + possible_workflow_instance_types: [] + willOpenModal: -> + @set 'possible_users', @store.peekAll('user') + @set 'possible_workflow_instance_types', @store.peekAll('panda/workflow-instance-type') + @get('model.users').then (users)=> + @set 'project_users', users.toArray() + #@set 'project_users', @get('model.users').toArray() + + actions: + saveRecord: -> + @set('model.users', @get('project_users')) + @_super() + + selectWorkflowInstanceType: (workflow_instance_type) -> + @set 'model.workflow_instance_type', workflow_instance_type + newProjectScenario: -> + record = @store.createRecord 'panda/project-scenario', project: @get('model') + @modal 'project/edit-project-scenario', + model: record + title: t('action.new_model', model: t('models.project_scenario')) + diff --git a/app/components/project/modals/edit-projects-context.coffee b/app/components/project/modals/edit-projects-context.coffee new file mode 100644 index 0000000..7ce8937 --- /dev/null +++ b/app/components/project/modals/edit-projects-context.coffee @@ -0,0 +1,7 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' + +export default ModalBase.extend + workflow_instance_types: Ember.computed -> + @store.peekAll('panda/workflow-instance-type') + actions: + newWorkflowInstanceType: -> @store.createRecord('panda/workflow-instance-type') diff --git a/app/components/project/modals/edit-workflow-group.coffee b/app/components/project/modals/edit-workflow-group.coffee new file mode 100644 index 0000000..f60c520 --- /dev/null +++ b/app/components/project/modals/edit-workflow-group.coffee @@ -0,0 +1,71 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import Changeset from 'ember-changeset' +export default ModalBase.extend + organization_tracker_subjects: Ember.A() + constraint_workflow_groups: Ember.A() + offset_selectable_workflow_groups: [] + + offset_type_none: Ember.computed.equal 'model.offset_type', 'none' + offset_type_connected_finish: Ember.computed.equal 'model.offset_type', 'connected_finish' + offset_type_connected_start: Ember.computed.equal 'model.offset_type', 'connected_start' + + willOpenModal: -> + @set "constraint_workflow_groups", @get('model.project.sorted_workflow_groups').rejectBy('id', @get('model.id')) + @set_offset_selectable_workflow_groups() + changeset = new Changeset(@get('model')) + @set 'model', changeset + @store.findAll('panda/tracker-subject', backgroundReload: false).then (tracker_subjects)=> + organization_tracker_subjects = Ember.A() + current_tracker_groups = @get('model.tracker_groups') + tracker_subjects.filterBy("edit_from_workflow_group").forEach (tracker_subject)-> + current_tracker_subject_item = current_tracker_groups.findBy("tracker_subject.id", tracker_subject.id) + organization_tracker_subjects.push Ember.Object.create + model: tracker_subject + current_tracker_group: current_tracker_subject_item + @set "organization_tracker_subjects", organization_tracker_subjects + + set_offset_selectable_workflow_groups: (offset_type) -> + offset_type ||= @get('model.offset_type') + return if offset_type is 'none' + selectable_workflow_groups = @get('model.project.sorted_workflow_groups') #.rejectBy('offset_type', 'none') allow all groups, more user friendly + @set 'offset_selectable_workflow_groups', selectable_workflow_groups + actions: + saveRecord: -> + #if offset_value = @get('model.offset_value') + # @set 'model.offset_value', parseInt(offset_value) + #if duration_value = @get('model.duration_value') + # @set 'model.duration_value', parseInt(duration_value) + #if @get("model.isNew") + # return @get("model").save() # add extra functions, do not close modal + @set("model.tracker_groups", @get("organization_tracker_subjects").mapBy("current_tracker_group").compact()) + @get('model.constraints').forEach (constraint)-> + constraint_workflow_group_id = constraint.get('constraint_workflow_group.id') + constraint.get('workflow_action_definitions').rejectBy('workflow_group.id', constraint_workflow_group_id).forEach (illigal_workflow_group_action)-> + constraint.get('workflow_action_definitions').removeObject illigal_workflow_group_action + + #workflow_action_definitions.forEach (workflow_action_definition)-> + # debugger + # workflow_action_definitions.removeObject(workflow_action_definition) unless workflow_action_definition.get("workflow_group.id") is constraint_workflow_group_id + # # remove non constraint_workflow_action_definition actions, switching the constraint_workflow_group does not remove these + # #constraint.set "workflow_action_definitions", workflow_action_definitions.filterBy("id", constraint_workflow_group_id) + #debugger + constraint.save() + @_super() + @incrementProperty('model.project.update_revision') + rollback_and_close: -> + constraints = @get('model.constraints') + constraints.filterBy('isNew').invoke 'unloadRecord' + constraints.rejectBy('isNew').invoke 'rollbackAttributes' # not rolling back belongsTo, known issue + @_super() + + set_offset_type: (offset_type) -> + offset_type ||= 'none' + @set 'model.offset_base', null + #TODO: CLEANUP DEPENDENCY TREE FARTHER AWAY FROM REFERENCE DATE DEPENDENT ON MODEL + @set 'model.offset_type', offset_type + @set_offset_selectable_workflow_groups offset_type + + newConstraint: -> + constraint = @store.createRecord('panda/workflow-group-constraint', workflow_group: @get('model')) + set_batch_element_threshold_unit: (unit) -> @set 'model.batch_element_threshold_unit', unit + diff --git a/app/components/project/modals/load-project-instances.coffee b/app/components/project/modals/load-project-instances.coffee new file mode 100644 index 0000000..b35ec5f --- /dev/null +++ b/app/components/project/modals/load-project-instances.coffee @@ -0,0 +1,116 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +{get} = Ember +export default ModalBase.extend + loading_message: '' + finished_message: '' + willOpenModal: -> + @set 'loading_message', "Loading project's dependencies" + @get('model').reload().then => @set 'loading_message', '' + actions: + load_instance_list: (file) -> + component = @ + store = @store + now = moment() + @set 'alert_message', '' + @set 'finished_message', '' + failure = ((message) -> + @set 'alert_message', message + @set 'loading_message', '' + @set 'finished_message', '' + ).bind @ + progress_formatter = d3.format('%') + process_instance_line = ((remaining_lines, current_index, start_length)-> + unless remaining_lines.length + @set 'alert_message', '' + @set 'loading_message', '' + @set 'finished_message', "Finished loading #{start_length} records" + return + instance_line = remaining_lines.splice(0, 1)[0] + current_index += 1 + project = @get('model') + [identifier, reference_date_spec, scenario_identifier, batch_size] = instance_line.split(/\s*[,;\t]\s*/).map((str) -> str.trim()) + scenario_lookup_attribute = if scenario_identifier.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) then 'identifier' else 'name' + batch_size = parseInt(batch_size) || 0 + @set 'loading_message', "Processing #{identifier} #{current_index} / #{start_length} (#{progress_formatter current_index / start_length}) of file #{file.get('name')}" + selected_scenarios = if scenario = @get('model.project_scenarios').findBy(scenario_lookup_attribute, scenario_identifier) then [scenario] else [] + @set 'selected_project_scenarios', selected_scenarios + workflow_groups = get_active_workflow_groups @get('model.workflow_groups'), selected_scenarios + workflow_action_definitions = get_active_workflow_action_definitions @get('model.workflow_action_definitions'), selected_scenarios + + if reference_date_spec + reference_date = moment(reference_date_spec) + state = 'uncategorized' + if scenario + state = 'unplanned' + if reference_date_spec + state = 'planned' + project_instance = store.createRecord 'panda/project-instance', + id: identifier + client_identifier: identifier + state: state + reference_date: reference_date + batch_size: batch_size + project: project + project_scenario: selected_scenarios.get('firstObject') + + # PROJECT GROUPS + new_project_instance_groups = [] + workflow_groups.forEach (workflow_group) -> + new_project_instance_groups.push store.createRecord 'panda/project-instance-group', + id: "pig-#{project_instance.id}-#{workflow_group.get('id')}" + project_instance: project_instance + workflow_group: workflow_group + project: project + + # PROJECT INSTANCES + if reference_date + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: workflow_action_definitions + reference_date: reference_date + batch_size: batch_size + result_nodes.forEach (node) -> + new_project_instance_action_date = get(node, 'date') + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', node.get('record.workflow_group.id')) + store.createRecord 'panda/project-instance-action', + id: "pia-#{project_instance.id}-#{node.get('record_id')}" + project_instance: project_instance + date: new_project_instance_action_date + state: if new_project_instance_action_date then (if new_project_instance_action_date.isBefore(now) then 'overdue' else 'planned') else 'pending' + workflow_action_definition: get(node, 'record') + project_instance_group: project_instance_group + project: project + else + workflow_action_definitions.forEach (workflow_action_definition) -> + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', workflow_action_definition.get('workflow_group.id')) + store.createRecord 'panda/project-instance-action', + id: "pia-#{project_instance.id}-#{workflow_action_definition.get('id')}" + project_instance: project_instance + state: 'pending' + workflow_action_definition: workflow_action_definition + project_instance_group: project_instance_group + project: project + process_instance_line(remaining_lines, current_index, start_length) + #setTimeout (-> process_instance_line(remaining_lines, current_index, start_length)), 2000 + ).bind @ + file_name = String(file.get('name')) + @set 'loading_message', "Uploading file '#{file_name}'..." + if file_name.match(/\.xlsx?$/) + return failure "Cannot upload excel files. Please use comma separated UTF-8 CSV files" + else unless file_name.match(/\.csv$/) + return failure "Please use comma separated UTF-8 CSV files" + file.readAsText().then (result) => + #instance_lines = result.split("\n").filter Ember.isPresent + #instance_lines.splice(0,1) # header + #process_instance_line instance_lines, 0, instance_lines.length + #return + try + return failure 'File does not have expected header: Batch identifier,Batch date,Scenario,Batch size' unless result.match(/^Batch identifier,Batch date,Scenario,Batch size\n/) + instance_lines = result.split("\n").filter Ember.isPresent + instance_lines.splice(0,1) # header + #setTimeout (-> process_instance_line instance_lines, 0, instance_lines.length), 3000 + process_instance_line instance_lines, 0, instance_lines.length + catch error + failure "Cannot load CSV
#{error?.message}" diff --git a/app/components/project/modals/new-project-from-contract.coffee b/app/components/project/modals/new-project-from-contract.coffee new file mode 100644 index 0000000..f44c9d1 --- /dev/null +++ b/app/components/project/modals/new-project-from-contract.coffee @@ -0,0 +1,53 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project' +validator = lookupValidator(ModelValidations) +import config from 'ember-get-config' +{get} = Ember +export default ModalBase.extend + session: Ember.inject.service('session') + contract: null + willOpenModal: -> + dummy = Ember.Object.create + name: '' # new project name + changeset = new Changeset(dummy, validator, ModelValidations) + @set 'changeset', changeset + + actions: + set_contract: (file) -> + @set 'file', file + failure = ((message) -> + @set 'contract', null + @set 'alert_message', message + ).bind @ + file.readAsText().then (result) => + try + contract = JSON.parse(result) + return failure 'Contract has no version' unless contract.contract_version? + @set 'contract', contract + @set 'alert_message', null + catch error + failure 'Cannot parse JSON' + saveRecord: -> + @get('changeset').validate() + component = @ + if @get('changeset.isValid') + file = @get('file') + model_name = 'panda/project' + base_url = @store.adapterFor(model_name).buildURL(model_name) + endpoint = "#{base_url}/new_from_contract" + session = @get('session') + #TODO implement generic request feature, this is not the way to do it! Done because it works and no time + if get(session, 'isAuthenticated') + session.authorize 'authorizer:devise', (key, authorization) => + file.upload(endpoint, data: {name: @get('changeset.name')}, headers: "#{key}": authorization).then (response) -> + if response.body?.errors?.length + component.set 'alert_message', response.body.errors.join('
') + component.set 'contract', null + else + component.store.pushPayload response.body if response.body?.data?.id + component.send 'close' + else # assume same host authenticated rails session + file.upload(endpoint, data: {name: @get('changeset.name')}).then (response) -> + component.send 'close' diff --git a/app/components/project/modals/position-workflow-action-definition-in-project.coffee b/app/components/project/modals/position-workflow-action-definition-in-project.coffee new file mode 100644 index 0000000..867349b --- /dev/null +++ b/app/components/project/modals/position-workflow-action-definition-in-project.coffee @@ -0,0 +1,42 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + actions: + position_workflow_action_definition_at_top_of: (workflow_action_definition, workflow_group) -> + Ember.run @, -> + workflow_action_definition.setProperties + position: 0 + workflow_group: workflow_group + workflow_action_definition.save() + working_position = 1 + workflow_group.get('sorted_workflow_action_definitions').forEach (group_workflow_action_definition) -> + return if group_workflow_action_definition.get('id') is workflow_action_definition.get('id') + if group_workflow_action_definition.get('position') isnt working_position + group_workflow_action_definition.set 'position', working_position + group_workflow_action_definition.save() + working_position += 1 + @send 'close' + + position_workflow_action_definition_below: (workflow_action_definition, workflow_action_definition_above) -> + workflow_group = workflow_action_definition_above.get('workflow_group') + working_position = 0 + Ember.run @, -> + workflow_group.get('sorted_workflow_action_definitions').forEach (group_workflow_action_definition) -> + return if group_workflow_action_definition.get('id') is workflow_action_definition.get('id') + if group_workflow_action_definition.get('position') isnt working_position + group_workflow_action_definition.set 'position', working_position + group_workflow_action_definition.save() + if group_workflow_action_definition.get('id') is workflow_action_definition_above.get('id') + working_position += 1 + workflow_action_definition.setProperties + position: working_position + workflow_group: workflow_group + workflow_action_definition.save() + working_position += 1 + @send 'close' + + unassign_workflow_action_definition: -> + workflow_action_definition = @get('model') + workflow_action_definition.set 'workflow_group', null + workflow_action_definition.save() + @send 'close' + diff --git a/app/components/project/modals/project-scenarios-show-grid.coffee b/app/components/project/modals/project-scenarios-show-grid.coffee new file mode 100644 index 0000000..236bb7f --- /dev/null +++ b/app/components/project/modals/project-scenarios-show-grid.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + actions: + edit_project_scenario: (project_scenario) -> + redirect = => + @modal 'project/project-scenarios-show-grid', + model: project_scenario.get('project') + @modal 'project/edit-project-scenario', + model: project_scenario + close: redirect + save: redirect + diff --git a/app/components/project/scenario-edit-item.coffee b/app/components/project/scenario-edit-item.coffee new file mode 100644 index 0000000..7edf4c1 --- /dev/null +++ b/app/components/project/scenario-edit-item.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'segment', 'project-scenario-edit-item-component'] + actions: + editProjectScenario: -> + @modal 'project/edit-project-scenario', + model: @get('model') + diff --git a/app/components/project/show/modals/dunlop-project-info.coffee b/app/components/project/show/modals/dunlop-project-info.coffee new file mode 100644 index 0000000..fce2857 --- /dev/null +++ b/app/components/project/show/modals/dunlop-project-info.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' + +export default ModalBase.extend + tab: 'index' + workflow_action_definition_nodes: Ember.computed 'model.workflow_action_definitions.@each.id', -> + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('model.workflow_action_definitions') + reference_date: moment() + result_nodes.filterBy('resolved').sortBy('absolute_value') + actions: + set_tab: (tab_name) -> @set 'tab', tab_name diff --git a/app/components/project/show/project-instance/modals/new-project-instance.coffee b/app/components/project/show/project-instance/modals/new-project-instance.coffee new file mode 100644 index 0000000..43d4dbe --- /dev/null +++ b/app/components/project/show/project-instance/modals/new-project-instance.coffee @@ -0,0 +1,120 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project-instance' + +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' + +export default ModalBase.extend + dependencies_loading: true + new_project_instance_groups: Ember.A() + new_project_instance_actions: Ember.A() + willOpenModal: -> + changeset = new Changeset(@get('model'), lookupValidator(ModelValidations), ModelValidations) + changeset.set 'project_scenario', null # otherwise non null promise object + @set 'model.changeset', changeset + @get('model').setProperties #TODO remove me, this is development logic + client_identifier: 'Ams-AAQ' + #id: "project-instance-#{@get('globals.modals_counter')}" + Ember.RSVP.all([ + @get('model.project.workflow_groups') + @get('model.project.workflow_action_definitions') + ]).then => + @set 'dependencies_loading', false + @send 'calculate_workflow_based_instances' + recalculate_workflow_based_instances: Ember.observer 'model.changeset.batch_size', -> + Ember.run.debounce @, @actions.calculate_workflow_based_instances, 350 + actions: + calculate_workflow_based_instances: -> + store = @store + Ember.run @, -> + workflow_groups = get_active_workflow_groups @get('model.project.workflow_groups'), @get('model.changeset.project_scenario') + workflow_action_definitions = get_active_workflow_action_definitions @get('model.project.workflow_action_definitions'), @get('model.changeset.project_scenario') + project_instance = @get('model') + project_instance.set 'client_identifier', project_instance.get('changeset.client_identifier') #TODO: REMOVE + #project = project_instance.get('project') + #project_instance.get('project_instance_groups').forEach (r) -> r and (r.rollbackAttributes(); r.unloadRecord()) + #project_instance.get('project_instance_groups').invoke 'unloadRecord' #TODO: REMOVE + #project_instance.get('project_instance_actions').invoke 'unloadRecord' #TODO: REMOVE + #project_instance.get('project_instance_groups').invoke 'rollbackAttributes' + #project_instance.get('project_instance_actions').invoke 'rollbackAttributes' + #@set 'model.project_instance_groups', Ember.A() + #@set 'model.project_instance_actions', Ember.A() + # PROJECT GROUPS + #new_project_instance_groups = [] + @set 'new_project_instance_groups', Ember.A() + @set 'new_project_instance_actions', Ember.A() + new_project_instance_groups = @get('new_project_instance_groups') + new_project_instance_actions = @get('new_project_instance_actions') + #new_project_instance_groups.invoke 'rollbackAttributes' + workflow_groups.forEach (workflow_group) -> + new_project_instance_groups.pushObject store.createRecord 'panda/project-instance-group', + #id: "#{project_instance.id}-project-instance-group-#{workflow_group.id}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance #IMPORTANT add after close + workflow_group: workflow_group + + now = moment() + reference_date = if selected_date = @get('model.changeset.reference_date') then moment(selected_date) else null + batch_size = @get('model.changeset.batch_size') || 0 + # PROJECT INSTANCES + if reference_date + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: workflow_action_definitions + reference_date: reference_date + batch_size: batch_size + result_nodes.forEach (wrapper) -> + new_project_instance_action_date = Ember.get(wrapper, 'date') + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', wrapper.get('record.workflow_group.id')) + new_project_instance_actions.pushObject store.createRecord 'panda/project-instance-action', + #id: "#{project_instance.id}-project-instance-action-#{wrapper.get('record_id')}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance + date: new_project_instance_action_date + state: if new_project_instance_action_date then (if new_project_instance_action_date.toISOString().substring(0,10) < now.toISOString().substring(0, 10) then 'overdue' else 'planned') else 'pending' + workflow_action_definition: Ember.get(wrapper, 'record') + project_instance_group: project_instance_group + else + workflow_action_definitions.forEach (workflow_action_definition) -> + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', workflow_action_definition.get('workflow_group.id')) + new_project_instance_actions.pushObject store.createRecord 'panda/project-instance-action', + #id: "#{project_instance.id}-project-instance-action-#{workflow_action_definition.id}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance + state: 'pending' + workflow_action_definition: workflow_action_definition + project_instance_group: project_instance_group + set_project_scenario: (project_scenario) -> + @set 'model.changeset.project_scenario', project_scenario + @set 'model.project_scenario', project_scenario #TODO: REMOVE + @send 'calculate_workflow_based_instances' + set_reference_date: (reference_date) -> + @set 'model.changeset.reference_date', reference_date + #@set 'model.reference_date', reference_date #TODO: REMOVE + @send 'calculate_workflow_based_instances' + close: -> + project = @get('model.project') + project_instance = @get('model') + @get('new_project_instance_groups').forEach (project_instance_group) -> project_instance_group.setProperties project: project, project_instance: project_instance + @get('new_project_instance_actions').forEach (project_instance_action) -> project_instance_action.setProperties project: project, project_instance: project_instance + @_super arguments... + + rollback_and_close: -> + project_instance = @get('model') + project_instance.get('project_instance_groups').invoke 'rollbackAttributes' + project_instance.get('project_instance_actions').invoke 'rollbackAttributes' + project_instance.rollbackAttributes() + #@get('model.changeset').rollback() + @send 'close' + #saveRecord: -> + # project = @get('model.project') + # component = @ + # @get('model').save().then (project_instance) -> + # #@get('model').save().then (project_instance) -> + # # component.get('new_project_instance_groups').forEach((project_instance_group) -> + # # project_instance_group.setProperties project: project, project_instance: project_instance + # # project_instance_group.save() + # # ) + # # component.get('new_project_instance_actions').forEach (project_instance_action) -> + # # project_instance_action.setProperties project: project, project_instance: project_instance + # # project_instance_action.save() + # # component.send 'close' diff --git a/app/components/project/workflow-action-constraints-graph.coffee b/app/components/project/workflow-action-constraints-graph.coffee new file mode 100644 index 0000000..4a25b40 --- /dev/null +++ b/app/components/project/workflow-action-constraints-graph.coffee @@ -0,0 +1,54 @@ +import Ember from 'ember' +import { mapHasMany } from 'ember-cli-dunlop/utils/map-action' +import Viz from 'npm:viz.js' # browserify +export default Ember.Component.extend + #workflow_action_constraints: mapHasMany 'project.workflow_action_definitions', 'workflow_action_constraints' + has_content: true + selected_project_scenario: null + + digraph_content: Ember.computed 'project.update_revision', 'active_workflow_action_definitions.@each.id', -> + labels = '' + connections = '' + component = @ + return unless @get('active_workflow_action_definitions.length') + active_workflow_action_definition_ids = @get('active_workflow_action_definitions').mapBy('id').toArray() + @get('active_workflow_action_definitions').forEach (workflow_action_definition) -> + constraint_connections = workflow_action_definition.get('workflow_action_connections').filterBy('connection_type', 'constraint') + #return unless workflow_action_definition.get('workflow_action_constraints.length') + return unless constraint_connections.get('length') + workflow_action_definition_id = "q#{workflow_action_definition.id}" # start with a letter + unless labels.indexOf(workflow_action_definition_id) > -1 + labels += " #{workflow_action_definition_id} [label=\"#{workflow_action_definition.get('title')}\" tooltip=\"#{workflow_action_definition.get('workflow_group.name')}\"];\n" + constraint_connections.forEach (constraint) -> + return if constraint.get('isDeleted') + #return unless constraint.get('constraint_workflow_action_definition.id') in active_workflow_action_definition_ids + constraint_workflow_action_definition_id = "q#{constraint.get('base_workflow_action_definition.id')}" # start with a letter + unless labels.indexOf(constraint_workflow_action_definition_id) > -1 + labels += " #{constraint_workflow_action_definition_id} [label=\"#{constraint.get('base_workflow_action_definition.title')}\" tooltip=\"#{constraint.get('base_workflow_action_definition.workflow_group.name')}\"];\n" + #actions_label = " label=\"#{constraint.get('type')}\"" + actions_label = " label=\"☝\"" + #if constraint.get('type') is "restrict" + # connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#C0392B\" penwidth=2 #{actions_label}];\n" + #else + # connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + #connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + connections += " #{workflow_action_definition_id} -> #{constraint_workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + #@set 'has_content', !!labels + "digraph \"Constraints diagram\" {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\" shape=box];\n rankdir=TB;\n#{labels}#{connections}}" + + svg_content: Ember.computed "digraph_content", -> + data = @get('digraph_content') + svg = Viz(data, format: 'svg') + svg.htmlSafe() + + attach_events: Ember.on 'didRender', Ember.observer 'svg_content', -> + component = @ + @$('.edge').each (index, edge) => + edge = $(edge) + return unless title = edge.find('title').text() + target_workflow_action_definition_id = title.replace(/.*->q/, '') + return unless target_workflow_action_definition = @store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + edge.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + diff --git a/app/components/project/workflow-action-offsets-graph.coffee b/app/components/project/workflow-action-offsets-graph.coffee new file mode 100644 index 0000000..d62005d --- /dev/null +++ b/app/components/project/workflow-action-offsets-graph.coffee @@ -0,0 +1,227 @@ +import Ember from 'ember' +import { mapHasMany } from 'ember-cli-dunlop/utils/map-action' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +import Viz from 'npm:viz.js' # browserify +GraphWrapper = Ember.Object.extend {} +critical_path_box_color = '#ccfff2' +secondary_resolve_box_color = '#ffe1b8' +higher_resolve_box_color = '#ffe1b8' +base_x_box_color = '#b9e0ff' +unresolved_node_box_color = '#c2f9c0' +attached_node_box_color = '#dddddd' +attached_node_connection_color = '#aaaaaa' +info_node_box_color = '#f7f7f7' +info_node_connection_color = '#dddddd' +constraint_node_box_color = '#ffcc7d' +constraint_node_connection_color = '#ff5d5d' +{get} = Ember +export default Ember.Component.extend + digraph_contents: [] + classNames: ['workflow-action-offsets-graph-component'] + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + has_content: false + choose_reference_date: true + graph_title: '' + show_attached: true + show_pointing_infos: false + show_pointing_constraints: false + active_workflow_action_definitions: [] + active_project_scenario: null + + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return 'X' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + didReceiveAttrs: -> + console.log 'Reload workflow-action-offset-graph call from after render' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + #_graph_observer: Ember.observer 'project.update_revision', '{chosen_absolute_date,reference_date_day_of_week,batch_size_simulation_value,show_connected}', 'active_workflow_action_definitions.@each.id', -> + # console.log 'Reload workflow-action-offset-graph call from observer' + # Ember.run.debounce @, @_setup_graph, 150 + _graph_observer: Ember.observer 'project.update_revision', '{show_attached,show_pointing_infos,show_pointing_constraints}', -> + console.log 'Reload workflow-action-offset-graph call from observer' + Ember.run.debounce @, @_setup_graph, 150 + #init: -> + # @_super arguments... + # #@set 'batch_size_simulation_value', 0 + @get('project.batch_size_lower_value') if @get('project.batch_size_lower_value') + # @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + _setup_graph: -> + @set 'digraph_contents', [] + digraph_labels = '' + digraph_connections = '' + component = @ + scenario = @get('active_project_scenario') + show_pointing_infos = @get('show_pointing_infos') + show_pointing_constraints = @get('show_pointing_constraints') + show_attached = @get('show_attached') + return unless @get('active_workflow_action_definitions.length') + + if @get('chosen_absolute_date') + reference_date = moment(@get('chosen_absolute_date')) + else + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week = @get('reference_date_day_of_week') # updates reference_date instance + batch_size = @get('batch_size_simulation_value') + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('active_workflow_action_definitions') + reference_date: reference_date + batch_size: batch_size + active_project_scenario: scenario + result_nodes.forEach (node) -> + return unless node.resolved or node.offset_wrappers?.length or node.get('record.dependent_workflow_action_offsets.length') + node.offset_wrappers ||= [] # iteration security + workflow_action_definition_id = "q#{node.get('record_id')}" # start with a letter + label_title = "#{node.get('record.title')}" + if node.resolved + label_title += "\\n#{component.days_offset_format(node.get('absolute_value'))}" + label_title += " (#{value_shift}d)" if value_shift = node.get('value_shift') # since X-based boxes don't have a connector displaying the value_shift. NOTE: not absolute + fill_color = if node.resolved then null else unresolved_node_box_color + farthest_absolute_value = Math.max(node.offset_wrappers.map((o) -> Math.abs(o.resulting_absolute_value))...) || 0 + inversed_lookup = node.resolve_type is 'offset-lookup-inversion' + #Ember.makeArray(node.offset_wrappers).forEach (node_offset) -> + node.offset_wrappers.forEach (node_offset) -> + base = if inversed_lookup then node_offset.get('record.workflow_action_definition.content') else node_offset.get('record.base_workflow_action_definition.content') + if base + base_id = "q#{base.get('id')}" + penwidth = if Math.abs(node_offset.resulting_absolute_value) is farthest_absolute_value then 2 else 1 + connection_label = "#{Math.abs(node_offset.total_offset)}d" + connection_label += " (#{Math.abs(node_offset.value_shift)}d)" if node_offset.value_shift + if node_offset.total_offset < 0 + connection_color = '#f5b042' + if not inversed_lookup + digraph_connections += "#{workflow_action_definition_id} -> #{base_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + digraph_connections += "#{base_id} -> #{workflow_action_definition_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + connection_color = '#f5b041' + if not inversed_lookup + digraph_connections += "#{base_id} -> #{workflow_action_definition_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + digraph_connections += "#{workflow_action_definition_id} -> #{base_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + if not fill_color + if node.resolve_level <= 1 + fill_color = critical_path_box_color + else if node.resolve_level is 2 + fill_color = secondary_resolve_box_color + else + fill_color = higher_resolve_box_color + + else + fill_color ||= base_x_box_color + digraph_labels += " #{workflow_action_definition_id} [label=\"#{label_title}\" fillcolor=\"#{fill_color}\" tooltip=\"#{node.get('record.workflow_group.name')}\"];\n" + + if show_pointing_infos + node.get('record.pointing_workflow_action_connections').filterBy('connection_type', 'info').forEach (info_connection) -> + pointing_action = info_connection.get('workflow_action_definition') + if scenario + return if info_connection.get('project_scenario.id') and info_connection.get('project_scenario.id') isnt scenario.get('id') + return if scenario.get('id') not in pointing_action.get('project_scenarios').mapBy('id') + info_id = "qi#{pointing_action.get('id')}" + connection_label = 'ℹ' + if digraph_labels.indexOf(info_id) < 0 + info_label_title = "#{pointing_action.get('title')}" + digraph_labels += " #{info_id} [label=\"#{info_label_title}\" fillcolor=\"#{info_node_box_color}\" tooltip=\"#{pointing_action.get('workflow_group.name')}\"];\n" + digraph_connections += "#{info_id} -> #{workflow_action_definition_id} [color=\"#{info_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + if show_pointing_constraints + node.get('record.pointing_workflow_action_connections').filterBy('connection_type', 'constraint').forEach (constraint_connection) -> + pointing_action = constraint_connection.get('workflow_action_definition') + if scenario + return if constraint_connection.get('project_scenario.id') and constraint_connection.get('project_scenario.id') isnt scenario.get('id') + return if scenario.get('id') not in pointing_action.get('project_scenarios').mapBy('id') + constraint_id = "qi#{pointing_action.get('id')}" + connection_label = '☝' + if digraph_labels.indexOf(constraint_id) < 0 + constraint_label_title = "#{pointing_action.get('title')}" + digraph_labels += " #{constraint_id} [label=\"#{constraint_label_title}\" fillcolor=\"#{constraint_node_box_color}\" tooltip=\"#{pointing_action.get('workflow_group.name')}\"];\n" + digraph_connections += "#{constraint_id} -> #{workflow_action_definition_id} [color=\"#{constraint_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + + if show_attached + # attached nodes might be pointed at, but never are pointing themselves. Whe have no knowledge about if these nodes or even the target nodes are already available on the canvas + # so do some checking. Attached nodes that are not yet available on the canvas will have no offset + @get('active_workflow_action_definitions').filterBy('offset_type', 'attached').forEach (attached_workflow_action_definition) => + # initialization + return unless connection = attached_workflow_action_definition.get('workflow_action_connections').findBy('connection_type', 'attached') + return unless connection_target_workflow_action_definition = connection.get('base_workflow_action_definition') + + # base label + attached_workflow_action_definition_id = "q#{attached_workflow_action_definition.get('id')}" + if digraph_labels.indexOf(attached_workflow_action_definition_id) < 0 + attached_label_title = "#{attached_workflow_action_definition.get('title')}" + digraph_labels += " #{attached_workflow_action_definition_id} [label=\"#{attached_label_title}\" fillcolor=\"#{attached_node_box_color}\" tooltip=\"#{attached_workflow_action_definition.get('workflow_group.name')}\"];\n" + + # target label + connection_target_workflow_action_definition_id = "q#{connection_target_workflow_action_definition.get('id')}" + if digraph_labels.indexOf(connection_target_workflow_action_definition_id) < 0 + connection_target_label_title = "#{connection_target_workflow_action_definition.get('title')}" + digraph_labels += " #{connection_target_workflow_action_definition_id} [label=\"#{connection_target_label_title}\" fillcolor=\"#{unresolved_node_box_color}\" tooltip=\"#{connection_target_workflow_action_definition.get('workflow_group.name')}\"];\n" + + # add the connection + connection_label = connection.get('conditional_base_value') || '' + connection_label = '✓' if connection_label is 'true' + digraph_connections += "#{attached_workflow_action_definition_id} -> #{connection_target_workflow_action_definition_id} [color=\"#{attached_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + content = "digraph \"Offset diagram\" {\n ratio=fill;\n node [style=filled fillcolor=\"#{critical_path_box_color}\" shape=box];\n rankdir=TB;\n#{digraph_labels}#{digraph_connections}}" + @set 'has_content', true + @get('digraph_contents').push content + + + svg_contents: Ember.computed "digraph_contents.length", -> + return '' unless @get('digraph_contents.length') + result = Ember.A() + data = @get('digraph_contents').forEach (data) -> + svg = Viz data, + format: 'svg' + engine: 'dot' + svg = svg.replace(/\ + component = @ + @$('.edge').each (index, edge_element) -> + edge = $(edge_element) + return unless title = edge.find('title').text() + return unless color = edge.find('path:first').attr('stroke') + return unless color in ['#f5b041', '#f5b042'] # two flavors + if color is '#f5b042' # determines what the defining action is + target_workflow_action_definition_id = title.replace(/->.*/, '').replace(/^q/, '') + else + target_workflow_action_definition_id = title.replace(/.*->q/, '') + return unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + edge.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + @$('.node').each (index, node_element) -> + node = $(node_element) + return unless title = node.find('title').text() + target_workflow_action_definition_id = title.replace(/^qi?/, '') + return unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + node.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + + actions: + batch_size_simulation_change: (value) -> + #@incrementProperty 'project.update_revision' + @set 'batch_size_simulation_value', Math.round(value) + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false diff --git a/app/components/project/workflow-group-offset-diagram.coffee b/app/components/project/workflow-group-offset-diagram.coffee new file mode 100644 index 0000000..abb13c3 --- /dev/null +++ b/app/components/project/workflow-group-offset-diagram.coffee @@ -0,0 +1,371 @@ +import Ember from 'ember' +import EmberCPM from 'ember-cpm' +{ Macros: { sum, difference, product } } = EmberCPM +# SOURCES +# http://bl.ocks.org/kiranml1/6872226 +# https://bost.ocks.org/mike/bar/2/ +# https://bl.ocks.org/hrecht/f84012ee860cb4da66331f18d588eee3 + +GraphWrapper = Ember.Object.extend + record: null + start: null + finish: null + init: -> + @_super arguments... + @set 'start', value: 0, date: null, has_variable_value_part: false + @set 'finish', value: 0, date: null, has_variable_value_part: false + duration: Ember.computed 'start.value', 'finish.value', -> + @get('finish.value') - @get('start.value') +export default Ember.Component.extend + classNames: ['workflow-group-offset-diagram-component'] + bar_height: 20 + x_axis_space: 60 + left_space: 250 + svg_width: 1200 + number_of_groups: Ember.computed.alias 'workflow_groups.length' + graph_height: product 'number_of_groups', 'bar_height' + all_workflow_groups: Ember.computed.alias 'active_workflow_groups' + workflow_groups: Ember.computed 'all_workflow_groups.@each.offset_type', 'all_workflow_groups.@each.duration_value', -> + @get('all_workflow_groups').filter((r) -> r.get('offset_type') isnt 'none').sortBy('position') + range_start_default: null + range_start: null + range_end: null + range_bounds: {min: -7, max: 7} + range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property() + #range_formatter: [{to: ((v)->v)}, {to: ((v)->v)}] + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise + chosen_absolute_date: null + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + #all_offset_values: Ember.computed.mapBy 'workflow_groups', 'offset_values' + #uniq_offset_values: Ember.computed.uniq 'all_offset_values' + #offset_values: Ember.computed 'uniq_offset_values', -> @get('uniq_offset_values').sort() + #min_offset_value: Ember.computed.alias 'offset_values.firstObject' + #max_offset_value: Ember.computed.alias 'offset_values.lastObject' + #max_end_time: Ember.computed 'workflow_groups.@each.offset_value', 'workflow_groups.@each.duration_value', -> + # max_end_time = -1e-10 + # @get('workflow_groups').forEach (workflow_group) -> + # end_time = workflow_group.get('offset_value') + workflow_group.get('duration_value') + # max_end_time = end_time if end_time > max_end_time + # max_end_time + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return '' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + actions: + range_changed: ([range_start, range_end]) -> + [range_start, range_end] = [Math.round(range_start), Math.round(range_end)] + @setProperties + range_start_default: [range_start, range_end] + range_start: range_start + range_end: range_end + + batch_size_simulation_change: (value) -> + @setProperties + range_start: null + range_end: null + batch_size_simulation_value: Math.round(value) + + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false + + #didReceiveAttrs: -> + init: -> + @_super arguments... + @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + didReceiveAttrs: -> + console.log 'Reload workflow-group-offset-diagram call from after render' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + _graph_observer: Ember.observer 'workflow_groups.@each.{offset_value,name,duration_value}', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', -> + console.log 'Reload workflow-group-offset-diagram call from observer' + Ember.run.debounce @, @_setup_graph, 150 + + _setup_graph: -> + console.log 'Reload workflow-group-offset-diagram' + container_selector = '.workflow-group-offset-diagram-container' + component = @ + @$(container_selector).html('').append '' + workflow_groups = @get('workflow_groups').toArray() + return unless workflow_groups.length + active_workflow_group_ids = workflow_groups.mapBy('id').toArray() + + # create conditioned workflow_groups + reference_date_day_of_week = @get('reference_date_day_of_week') + if @get('chosen_absolute_date') + reference_date = moment(@get('chosen_absolute_date')) + else + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance + + wrappers = [] + x_min = -1 # at least include zero + x_max = 0 + batch_size_simulation_value = @get('batch_size_simulation_value') + # recursive adder + add_dependent_wrapper_to_wrappers = (base_wrapper, record) -> + wrapper = GraphWrapper.create + record: record + if record.get('offset_type') is 'connected_finish' + wrapper.set 'finish.value', base_wrapper.get('start.value') - (record.get('offset_value') || 0) + wrapper.set 'start.constant_value', wrapper.get('finish.value') - (record.get('duration_value') || 0) + if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value') + # see how many times the threshold is exceeded. Within the threshold means no extra time + threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold + if threshold_exceedance + wrapper.set 'has_variable_value_part', true + period_unit = wrapper.get('record.batch_element_threshold_unit') + base_date = reference_date.clone().add(wrapper.get('start.constant_value'), 'day') + if period_unit is 'businessday' + wrapper.set 'start.date', base_date.clone().businessSubtract(threshold_exceedance) + else + wrapper.set 'start.date', base_date.clone().subtract(threshold_exceedance, period_unit) + wrapper.set 'start.value', wrapper.get('start.constant_value') - base_date.diff(wrapper.get('start.date'), 'day') + else + wrapper.set 'start.value', wrapper.get('start.constant_value') + else + wrapper.set 'start.value', wrapper.get('start.constant_value') + else #if record.get('offset_type') is 'connected_start' + wrapper.set 'start.value', base_wrapper.get('finish.value') + (record.get('offset_value') || 0) + wrapper.set 'finish.constant_value', wrapper.get('start.value') + (record.get('duration_value') || 0) + if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value') + if threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold + wrapper.set 'has_variable_value_part', true + period_unit = wrapper.get('record.batch_element_threshold_unit') + base_date = reference_date.clone().add(wrapper.get('finish.constant_value')) + if period_unit is 'businessday' + wrapper.set 'finish.date', base_date.clone().businessAdd(threshold_exceedance) + else + wrapper.set 'finish.date', base_date.clone().add(threshold_exceedance, period_unit) + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + wrapper.get('finish.date').diff(base_date, 'day') + else + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + else + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + wrapper.set 'start.date', reference_date.clone().add(wrapper.get('start.value'), 'day') unless wrapper.get('start.date') + wrapper.set 'finish.date', reference_date.clone().add(wrapper.get('finish.value'), 'day') unless wrapper.get('finish.date') + x_min = Math.min(x_min, wrapper.get('start.value')) + x_max = Math.max(x_max, wrapper.get('finish.value')) + wrappers.push wrapper + record.get('offset_dependents').forEach (offset_dependent_record) -> + add_dependent_wrapper_to_wrappers wrapper, offset_dependent_record if offset_dependent_record.get('id') in active_workflow_group_ids + + workflow_groups.rejectBy('offset_base.content').forEach (record) -> + wrapper = GraphWrapper.create + record: record + add_dependent_wrapper_to_wrappers wrapper, record + wrappers = wrappers.sortBy('record.position') + x_max += 1 # design space at the right + + if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max + @set 'range_bounds', min: x_min, max: x_max + @set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts + else if not @get('range_start_default') # uninitialized + @set 'range_start_default', [x_min, x_max] + + graph_min = @get('range_start') || x_min + graph_max = @get('range_end') || x_max + + graph_range_width = @get('svg_width') - @get('left_space') - 22 + x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width]) + #yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')]) + y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(wrappers.mapBy('record.name').reverse()) + + x_ticks = [] + wrappers.forEach (wrapper) -> + [start, finish] = wrapper.getPropertyValues('start.value', 'finish.value') + x_ticks.push start if x_ticks.indexOf(start) < 0 + x_ticks.push finish if x_ticks.indexOf(finish) < 0 + #workflow_groups.forEach (workflow_action_definition) -> + # offset_value = workflow_action_definition.get('offset_value') + # x_ticks.push offset_value unless offset_value is 0 or x_ticks.indexOf(offset_value) > -1 + # if duration_value = workflow_action_definition.get('duration_value') + # end_time = offset_value + duration_value + # x_ticks.push end_time unless end_time is 0 or x_ticks.indexOf(end_time) > -1 + x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending) + # now remove the ticks not having enough space + displayed_x_ticks = [] + if x_ticks.length + displayed_x_ticks.push x_ticks[0] + latest_pixel_x_for_space = x(x_ticks[0]) + x_ticks.forEach (v) -> + current_pixel_x = x(v) + if current_pixel_x - latest_pixel_x_for_space > 14 + latest_pixel_x_for_space = current_pixel_x + displayed_x_ticks.push v + + svg_height = @get('x_axis_space') + @get('graph_height') + canvas = d3.select("#{container_selector} svg") + .attr 'width', '100%' + .attr 'height', svg_height + .attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}" + + lines_data = x_ticks + lines_data = lines_data.slice(1) if x(lines_data[0]) is graph_min # first element is y-axis, so no tick + lines = canvas.append('g') + .attr('class', 'offset-lines') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('line') + .data(lines_data) + .enter() + .append('line') + .attr( + x1: (d) -> x(d) + y1: 0 + x2: (d) -> x(d) + y2: @get('graph_height') + 5 + class: 'offset-line' + ) + + if graph_max >= 0 and graph_min <= 0 # base line is zero + base_line = canvas.append('line').attr + x1: x(0) + y1: 0 + x2: x(0) + y2: 15 + @get('graph_height') + class: 'base-line' + transform: "translate(#{@get('left_space')},0)" + + bars_grid = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'bars') + + bars = bars_grid.selectAll('.bar') + .data(wrappers.filter((d) -> (d.get('finish.value') > graph_min) and (d.get('start.value') < graph_max))) + .enter() + bars.append('rect') + .attr('class', (d) -> "bar lead-time-#{d.get('duration') || 'none'}") + .attr('x', (d) -> x(Math.max(d.get('start.value'), graph_min))) + .attr('y', (d) -> y(d.get('record.name'))) + .attr('height', y.rangeBand()) + .attr 'width', (d) -> + if duration = d.get('duration') + display_range = duration + if d.get('start.value') < graph_min # remove difference from width + display_range = display_range + d.get('start.value') - graph_min + if d.get('finish.value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('finish.value') + graph_range_width * display_range / (graph_max - graph_min) + else + # no duration to display, clickable width, gets different styling + 5 + .on 'click', (wrapper) => + return unless wrapper?.get('record') + @modal 'project/edit-workflow-group', + model: wrapper.get('record') + + wrappers_having_extra_duration_due_to_batch_simulation = wrappers.filter (d) -> + return false unless d.get('has_variable_value_part') + if d.get('record.offset_type') is 'connected_finish' + d.get('start.constant_value') > graph_min and d.get('start.value') < graph_max + else + d.get('finish.value') > graph_min and d.get('finish.constant_value') < graph_max + if wrappers_having_extra_duration_due_to_batch_simulation.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'bars-parts-indicating-extra-duration-due-to-batch-size') + .selectAll('.bar') + .data(wrappers_having_extra_duration_due_to_batch_simulation) + .enter() + .append('rect') + .attr('class', "bar extra-duration") + .attr('x', (d) -> + if d.get('record.offset_type') is 'connected_finish' + x(Math.max(d.get('start.value'), graph_min)) + else + x(Math.max(d.get('finish.constant_value'), graph_min)) + ) + .attr('y', (d) -> y(d.get('record.name'))) + .attr('height', y.rangeBand()) + .attr 'width', (d) -> + if d.get('record.offset_type') is 'connected_finish' + display_range = Math.abs(d.get('start.constant_value') - d.get('start.value')) + if d.get('start.value') < graph_min # remove difference from width + display_range = display_range + d.get('start.value') - graph_min + if d.get('start.constant_value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('start.constant_value') + graph_range_width * display_range / (graph_max - graph_min) + else + display_range = Math.abs(d.get('finish.value') - d.get('finish.constant_value')) + if d.get('finish.constant_value') < graph_min # remove difference from width + display_range = display_range + d.get('finish.constant_value') - graph_min + if d.get('finish.value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('finish.value') + graph_range_width * display_range / (graph_max - graph_min) + + + # bars crossing the left y-axis due to slider restrictions. Show text + groups_crossing_left_axis = wrappers.filter (d) -> + d.get('start.value') < graph_min and (d.get('finish.value') >= graph_min) # allow connecting edge, therefore >= in stead of > + if groups_crossing_left_axis.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'groups-crossing-left-axis') + .selectAll('text') + .data(groups_crossing_left_axis) + .enter() + .append('text') + .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height + .html((d) -> "← #{component.days_offset_format(d.get('start.value'))}") + + # bars crossing the right y-axis due to slider restrictions. Show text + groups_crossing_right_axis = wrappers.filter (d) -> + d.get('start.value') < graph_max and (d.get('finish.value') > graph_max) + if groups_crossing_right_axis.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'groups-crossing-right-axis') + .selectAll('text') + .data(groups_crossing_right_axis) + .enter() + .append('text') + .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height + .attr('x', graph_range_width) + .html((d) -> "#{component.days_offset_format(d.get('finish.value'))} →") + + yAxis = d3.svg.axis() + yAxis + .orient('left') + .scale(y) + .tickSize(1) + y_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class','y-axis') + .call(yAxis) + xAxis = d3.svg.axis() + .orient('bottom') + .scale(x) + .tickSize(1) + .tickValues(displayed_x_ticks) + .tickFormat component.days_offset_format.bind(component) + if displayed_x_ticks.length + x_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})") + .attr('class','x-axis') + .call(xAxis) + .selectAll('text') + .style('text-anchor', 'end') + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', "rotate(-40)") + canvas.selectAll('.y-axis .tick').on 'click', (d) => + return unless workflow_group = workflow_groups.findBy 'name', d + @modal 'project/edit-workflow-group', + model: workflow_group + + @set 'd3graph', canvas diff --git a/app/components/select-duration.coffee b/app/components/select-duration.coffee new file mode 100644 index 0000000..6864b3d --- /dev/null +++ b/app/components/select-duration.coffee @@ -0,0 +1,20 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['select-duration'] + value: 0 + unit: 'day' + week_days: (-> [1,2,3,4,5]).property() + unit_businessday: Ember.computed.equal 'unit', 'businessday' + week_days_index_sum: Ember.computed.sum 'week_days' + value_date_scope: Ember.computed 'value', -> + if Math.round(Math.abs(@get('value'))) is 1 then 'date.unit' else 'date.unit.plural' + has_default_business_days: Ember.computed 'week_days.length', 'week_days_index_sum', -> + @get("week_days.length") is 5 and @get("week_days_index_sum") is 15 + actions: + toggle_week_day_presence: (target, week_day, selected) -> + #target = target.get('batch_element_threshold_business_days') unless Ember.isArray(target) + if selected + target.addObject week_day.index + else + target.removeObject week_day.index diff --git a/app/components/select-week-days.coffee b/app/components/select-week-days.coffee new file mode 100644 index 0000000..664bc3c --- /dev/null +++ b/app/components/select-week-days.coffee @@ -0,0 +1,5 @@ +import Ember from 'ember' + +export default Ember.Component.extend + abc: 3 + diff --git a/app/components/set-workflow-group-tracker-group.coffee b/app/components/set-workflow-group-tracker-group.coffee new file mode 100644 index 0000000..3f6875b --- /dev/null +++ b/app/components/set-workflow-group-tracker-group.coffee @@ -0,0 +1,20 @@ +import Ember from 'ember' +export default Ember.Component.extend + isEditing: false + selectedViewItem: null + doubleClick: -> @toggleProperty("isEditing") + actions: + selectViewItem: (tracker_group, select_component)-> + if current_tracker_group = @get("current_tracker_group") + @get("workflow_group.tracker_groups").then (tracker_groups)-> + tracker_groups.removeObject current_tracker_group + unless tracker_group + @get("workflow_group").save() + return + + @get("workflow_group.tracker_groups").then (tracker_groups)-> + tracker_groups.pushObject(tracker_group) + @get("workflow_group").save() + set_selected: (-> + @set("selectedViewItem", @get("current_tracker_group")) + ).on("didReceiveAttrs") diff --git a/app/components/state-badge.js b/app/components/state-badge.js new file mode 100644 index 0000000..bba6080 --- /dev/null +++ b/app/components/state-badge.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/state-badge'; diff --git a/app/components/ui-multi-select.js b/app/components/ui-multi-select.js new file mode 100644 index 0000000..b9a18c6 --- /dev/null +++ b/app/components/ui-multi-select.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/ui-multi-select'; diff --git a/app/components/user/modals/edit-user.coffee b/app/components/user/modals/edit-user.coffee new file mode 100644 index 0000000..6129f4a --- /dev/null +++ b/app/components/user/modals/edit-user.coffee @@ -0,0 +1,17 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project' +validator = lookupValidator(ModelValidations) +export default ModalBase.extend + selected_organizations: [] + possible_organizations: [] + willOpenModal: -> + @set 'possible_organizations', @store.peekAll('panda/organization') + @get("model.organizations").then (organizations)=> + @set "selected_organizations", organizations.toArray() + + actions: + saveRecord: -> + @set("model.organizations", @get('selected_organizations')) + @_super() diff --git a/app/components/user/modals/new-user.coffee b/app/components/user/modals/new-user.coffee new file mode 100644 index 0000000..f542f66 --- /dev/null +++ b/app/components/user/modals/new-user.coffee @@ -0,0 +1,15 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/user' +validator = lookupValidator(ModelValidations) +export default ModalBase.extend + selected_organizations: [] + possible_organizations: [] + willOpenModal: -> + @set 'possible_organizations', @store.peekAll('panda/organization') + @get("model.organizations").then (organizations)=> + @set "selected_organizations", organizations.toArray() + user = @get('model') + changeset = new Changeset(user, validator, ModelValidations) + user.set 'changeset', changeset diff --git a/app/components/value-type-display.js b/app/components/value-type-display.js new file mode 100644 index 0000000..e365ff9 --- /dev/null +++ b/app/components/value-type-display.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/value-type-display'; diff --git a/app/components/workflow-action-definition-item.coffee b/app/components/workflow-action-definition-item.coffee new file mode 100644 index 0000000..2625513 --- /dev/null +++ b/app/components/workflow-action-definition-item.coffee @@ -0,0 +1,69 @@ +import Ember from 'ember' +export default Ember.Component.extend + classNames: ["workflow-action-definition-item", 'workflow-action-container'] + classNameBindings: ["workflow_action_definition.done:done", "isEditing:is-editing:is-display"] + selectedValueType: Ember.computed.oneWay "workflow_action_definition.value_type" + isEditing: false + + make_editable: (-> + @set 'isEditing', true + ).on "doubleClick" + + didRender: -> + @_super arguments... + #@set 'isEditing', true if @get('workflow_action_definition.isNew') + + actions: + saveWorkflowActionDefinition: -> + record = @get("workflow_action_definition") + first_save = record.get('isNew') + record.setProperties + value_type: @get("selectedValueType") + record.save().then => + @set 'isEditing', false + if first_save + @send('editWorkflowActionDefinition', record) + else + # fist save has no graph impact, so only overhere + @incrementProperty 'model.project.update_revision' + + destroyWorkflowActionDefinition: (workflow_action_definition)-> + @modal "confirm", + title_path: "general.destroy.are_you_sure" + model: @get("workflow_action_definition") + ok: -> + @set 'isEditing', false + workflow_action_definition = @get('model') + if workflow_action_definition.get('isNew') + workflow_action_definition.deleteRecord() + else + workflow_action_definition.rollbackAttributes() + workflow_action_definition.destroyRecord() + @incrementProperty 'model.project.update_revision' + rollbackWorkflowActionDefinition: -> + @set 'isEditing', false + @get('workflow_action_definition').rollbackAttributes() + + positionInProject: -> + @modal 'project/position-workflow-action-definition-in-project', + model: @get('workflow_action_definition') + @set 'isEditing', false + + authorize_users: -> + @modal 'workflow-action-definition/authorize-users', + title_path: 'workflow_action_definition.authorize_users.title' + model: @get('workflow_action_definition') + + editWorkflowActionDefinition: -> + @modal 'workflow-action-definition/edit-workflow-action-definition', + model: @get('workflow_action_definition') + @set 'isEditing', false # usability + + #select_on_edit: Ember.computed "workflow_action_definition.isEditing", -> + # @$('.workflow-action-definition-title-edit').select() + + updateWorkflowActionDefinitionIfNeeded: (-> + workflow_action_definition = @get('workflow_action_definition') + if workflow_action_definition.changedAttributes().done + workflow_action_definition.save() + ).observes('workflow_action_definition.done') diff --git a/app/components/workflow-action-definition/modal-header.coffee b/app/components/workflow-action-definition/modal-header.coffee new file mode 100644 index 0000000..01f82c2 --- /dev/null +++ b/app/components/workflow-action-definition/modal-header.coffee @@ -0,0 +1,4 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'message'] diff --git a/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee b/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee new file mode 100644 index 0000000..5a2a6d5 --- /dev/null +++ b/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee @@ -0,0 +1,333 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' + +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/workflow-action-definition' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' +import { value_type_options } from 'ember-panda/models/panda/workflow-action-definition' +ESC_KEY = 27 +Component = + _initEscListener: -> + closeOnEscapeKey = (ev) => + @send 'rollback_and_close' if ev.keyCode is ESC_KEY + Ember.$('body').on('keyup.modal-dialog', closeOnEscapeKey) + value_type_options: value_type_options + constraint_types: Ember.A(['warn', 'restrict']) + lastSelectedWorkflowGroup: null + lastSelectedWorkflowActionDefinition: null + selectedConstraintType: 'warn' + possible_organizations: [] + connection_types: ['info', 'constraint'] # could be warning in future as well + + new_workflow_action_constraint_warning: '' + new_workflow_action_offset_warning: '' + #new_workflow_action_offset_offset_value: 0 + #new_workflow_action_offset_offset_unit: 'day' + #new_workflow_action_offset_offset_business_days: (-> [ 1, 2, 3, 4, 5 ]).property() + #new_workflow_action_offset_batch_element_threshold_value: 0 + #new_workflow_action_offset_batch_element_threshold_unit: 'businessday' + #new_workflow_action_offset_batch_element_threshold_business_days: (-> [ 1, 2, 3, 4, 5 ]).property() + #select_action_offset_base_alert: '' + + attached_to_workflow_action: null + attached_to_workflow_action_workflow_group: null + possible_attached_to_workflow_action_workflow_action_definitions: [] + attached_to_connection_conditional_base_value: '' + attached_to_connection_warning: '' + + working_workflow_action_offsets: Ember.A() + non_deleted_working_workflow_action_offsets: Ember.computed 'working_workflow_action_offsets.@each.isDeleted', -> @get('working_workflow_action_offsets').rejectBy('isDeleted') + + working_pointing_workflow_action_connections: Ember.A() + non_deleted_working_pointing_workflow_action_connections: Ember.computed 'working_pointing_workflow_action_connections.@each.isDeleted', -> @get('working_pointing_workflow_action_connections').rejectBy('isDeleted') + + #working_workflow_action_constraints: Ember.A() + #used_workflow_action_definitions_for_offset: Ember.computed 'working_workflow_action_offsets.@each.isDeleted', -> + # @get('working_workflow_action_offsets').rejectBy('isDeleted').mapBy('base_workflow_action_definition.id') + #used_workflow_action_definitions_for_constraint: Ember.computed 'working_workflow_action_constraints.@each.isDeleted', -> + # @get('working_workflow_action_constraints').rejectBy('isDeleted').mapBy('constraint_workflow_action_definition.id') + + offset_type_none: Ember.computed.equal 'model.changeset.offset_type', 'none' + offset_type_attached: Ember.computed.equal 'model.changeset.offset_type', 'attached' + offset_type_configured: Ember.computed.equal 'model.changeset.offset_type', 'configured' + + willOpenModal: -> + @_initEscListener() + @get('model.workflow_group').then (workflow_group) => + @set 'lastSelectedWorkflowGroup', workflow_group + @set 'possible_organizations', @store.peekAll('panda/organization') + + # CONNECTION SETUP + if workflow_action_connection = @get('model.workflow_action_connections').findBy('connection_type', 'attached') + @set 'attached_to_workflow_action', workflow_action_connection.get('base_workflow_action_definition.content') + @set 'attached_to_workflow_action_workflow_group', @get('attached_to_workflow_action.workflow_group.content') + @set 'attached_to_connection_conditional_base_value', workflow_action_connection.get('conditional_base_value') + else # same group of configuring action + @set 'attached_to_workflow_action_workflow_group', @get('model.workflow_group.content') + unless @get('attached_to_workflow_action_workflow_group') # fallback to first available group + @set 'attached_to_workflow_action_workflow_group', @get('model.project.workflow_groups.firstObject') + if connection_group = @get('attached_to_workflow_action_workflow_group') + @set 'possible_attached_to_workflow_action_workflow_action_definitions', connection_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + + changeset = new Changeset(@get('model'), lookupValidator(ModelValidations), ModelValidations) + add_business_day_computations changeset, 'offset' + add_business_day_computations changeset, 'batch_element_threshold' + @set 'model.changeset', changeset + model_id = @get('model.id') + @get('model.workflow_action_offsets').then (workflow_action_offsets) => + @set('working_workflow_action_offsets', workflow_action_offsets.toArray()) + workflow_action_offsets.forEach (workflow_action_offset) -> + if selector_workflow_group = workflow_action_offset.get('base_workflow_action_definition.workflow_group.content') + workflow_action_offset.set 'selector_workflow_group', selector_workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', selector_workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', model_id) + base_action = workflow_action_offset.get('base_workflow_action_definition.content') + workflow_action_offset.set 'x_based', !base_action + workflow_action_offset.set 'original_base_workflow_action_definition', base_action + @get('model.pointing_workflow_action_connections').then (pointing_workflow_action_connections) => + @set('working_pointing_workflow_action_connections', pointing_workflow_action_connections.toArray()) + pointing_workflow_action_connections.forEach (pointing_workflow_action_connection) -> + if selector_workflow_group = pointing_workflow_action_connection.get('workflow_action_definition.workflow_group.content') + pointing_workflow_action_connection.set 'selector_workflow_group', selector_workflow_group + pointing_workflow_action_connection.set 'filtered_workflow_action_definitions', selector_workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', model_id) + original_action = pointing_workflow_action_connection.get('workflow_action_definition.content') + pointing_workflow_action_connection.set 'original_workflow_action_definition', original_action + #@get('model.workflow_action_constraints').then => @set('working_workflow_action_constraints', arguments[0].toArray()) + + actions: + #newWorkflowActionConstraint: (constraint_workflow_action_definition, event) -> + # @set 'new_workflow_action_constraint_warning', '' + # return unless constraint_workflow_action_definition + # $(event.target).val('') + # if @get('used_workflow_action_definitions_for_constraint').includes(constraint_workflow_action_definition.get('id')) + # @set 'new_workflow_action_constraint_warning', + # t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_already_used', constraint_workflow_action_definition.toJSON?()) + # return + # constraint = @store.createRecord 'panda/workflow-action-constraint', + # type: @get('selectedConstraintType') + # workflow_action_definition: @get('model') + # constraint_workflow_action_definition: constraint_workflow_action_definition + # @get('working_workflow_action_constraints').addObject constraint + + #newWorkflowActionOffset: (base_workflow_action_definition, event) -> + # base_workflow_action_definition = @get('attached_to_workflow_action') + # offset_value = parseInt(@get('new_workflow_action_offset_offset_value')) || 0 + # if @get('offset_type_workflow_action_definition') and not base_workflow_action_definition + # @set 'new_workflow_action_offset_warning', + # t('workflow_action_definition.edit_workflow_action_definition.no_action_offset_base_given_alert') + # return + # if @get('used_workflow_action_definitions_for_offset').includes(base_workflow_action_definition.get('id')) + # @set 'new_workflow_action_offset_warning', + # t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_already_used', base_workflow_action_definition.toJSON?()) + # @set 'attached_to_workflow_action', null + # return + # @set 'new_workflow_action_offset_warning', '' + # offset = @store.createRecord 'workflow-action-offset', + # offset_value: offset_value + # offset_unit: @get('new_workflow_action_offset_offset_unit') + # offset_business_days: @get('new_workflow_action_offset_offset_business_days') + # batch_element_threshold_value: @get('new_workflow_action_offset_batch_element_threshold_value') + # batch_element_threshold_unit: @get('new_workflow_action_offset_batch_element_threshold_unit') + # batch_element_threshold_business_days: @get('new_workflow_action_offset_batch_element_threshold_business_days') + # workflow_action_definition: @get('model') + # base_workflow_action_definition: base_workflow_action_definition + # @get('working_workflow_action_offsets').addObject offset + # @set 'attached_to_workflow_action', null # required for empty validation + + add_workflow_action_offset: -> + @set 'new_workflow_action_offset_warning', '' + workflow_action_offset = @store.createRecord 'panda/workflow-action-offset', + workflow_action_definition: @get('model') + x_based: true + @actions.set_selector_workflow_group.bind(@) workflow_action_offset, @get('model.workflow_group'), force_refresh: true + @get('working_workflow_action_offsets').pushObject workflow_action_offset + + set_base_workflow_action_definition: (workflow_action_offset, workflow_action_definition) -> + workflow_action_offset.set 'base_workflow_action_definition_warning', '' + workflow_action_offset.set 'base_workflow_action_definition', workflow_action_definition + + set_connection_workflow_action_definition: (connection, workflow_action_definition) -> + connection.set 'workflow_action_definition_warning', '' + connection.set 'workflow_action_definition', workflow_action_definition + + add_workflow_action_connection: -> + connection = @store.createRecord 'panda/workflow-action-connection', + base_workflow_action_definition: @get('model') + connection_type: @get('connection_types.firstObject') + @actions.set_connection_selector_workflow_group.bind(@) connection, @get('model.workflow_group'), force_refresh: true + @get('working_pointing_workflow_action_connections').pushObject connection + + + workflow_group_change: (workflow_group) -> + @set 'lastSelectedWorkflowGroup', workflow_group + @set 'lastSelectedWorkflowActionDefinition', null + + #destroyWorkflowActionConstraint: (workflow_action_constraint) -> + # if workflow_action_constraint.get('isNew') + # workflow_action_constraint.rollbackAttributes() + # @get('working_workflow_action_constraints').removeObject workflow_action_constraint + # else + # workflow_action_constraint.deleteRecord() + # @set 'new_workflow_action_constraint_warning', '' + + destroyWorkflowActionOffset: (workflow_action_offset) -> + if workflow_action_offset.get('isNew') + @get('working_workflow_action_offsets').removeObject workflow_action_offset + workflow_action_offset.rollbackAttributes() + else + workflow_action_offset.deleteRecord() + #@set 'new_workflow_action_offset_warning', '' + + destroyWorkflowActionConnection: (connection) -> + if connection.get('isNew') + @get('working_pointing_workflow_action_connections').removeObject connection + connection.rollbackAttributes() + else + connection.deleteRecord() + + selectOrganization: (organization) -> @set('model.organization', organization) + selectProjectScenario: (project_scenario) -> @set('model.project_scenario', project_scenario) + + saveRecord: -> + changeset = @get('model.changeset') + changeset.validate() + if @get('offset_type_configured') + ##if action_offset_base = @get('attached_to_workflow_action') + ## @set 'model.action_offset_base', action_offset_base + ##else + ## @set 'select_action_offset_base_alert', t('workflow_action_definition.edit_workflow_action_definition.no_action_offset_base_given_alert') + ## return + #if action_offset_base = @get('attached_to_workflow_action') + # @set 'new_workflow_action_offset_warning', t('workflow_action_definition.edit_workflow_action_definition.chosen_but_non_added_offset_workflow_action_definition') + # return + unless @get('non_deleted_working_workflow_action_offsets.length') + @set 'new_workflow_action_offset_warning', t('workflow_action_definition.edit_workflow_action_definition.offset_type_configured_but_no_offsets') + return + all_workflow_action_definitions_valid = true + @get('non_deleted_working_workflow_action_offsets').forEach (workflow_action_offset) -> + workflow_action_offset.set 'base_workflow_action_definition_warning', '' + if !workflow_action_offset.get('x_based') and !workflow_action_offset.get('base_workflow_action_definition.id') + workflow_action_offset.set 'base_workflow_action_definition_warning', t('workflow_action_definition.edit_workflow_action_definition.base_workflow_action_definition_not_present') + all_workflow_action_definitions_valid = false + return unless all_workflow_action_definitions_valid + else + @get('model.workflow_action_offsets').invoke 'rollbackAttributes' # only offsets if offset_type is 'configured' + #@set 'model.action_offset_base', null + #@set 'select_action_offset_base_alert', '' + + if @get('offset_type_attached') + unless @get('attached_to_workflow_action') + @set 'attached_to_connection_warning', t('workflow_action_definition.edit_workflow_action_definition.offset_type_attached_but_no_connection') + return + else + @set 'attached_to_connection_warning', '' + @set 'attached_to_workflow_action', null + + all_connections_valid = true + @get('working_pointing_workflow_action_connections').forEach (pointing_connection) -> + pointing_connection.set 'workflow_action_definition_warning', '' + unless pointing_connection.get('workflow_action_definition.content') + pointing_connection.set 'workflow_action_definition_warning', t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_not_present') + all_connections_valid = false + return unless all_connections_valid + + # This was validation phase, now perform persisting in proper order + return unless changeset.get('isValid') + changeset.save().then (workflow_action_definition) => + if @get('offset_type_configured') + @get('working_workflow_action_offsets').invoke 'save' + else + @get('working_workflow_action_offsets').invoke 'destroyRecord' + + @get('working_pointing_workflow_action_connections').invoke 'save' + + existing_attached_connection = @get('model.workflow_action_connections').findBy('connection_type', 'attached') + if @get('offset_type_attached') + if existing_attached_connection + existing_attached_connection.setProperties + base_workflow_action_definition: @get('attached_to_workflow_action') + conditional_base_value: @get('attached_to_connection_conditional_base_value') + existing_attached_connection.save() + else + new_attached_connection = @store.createRecord 'panda/workflow-action-connection', + workflow_action_definition: @get('model') + base_workflow_action_definition: @get('attached_to_workflow_action') + conditional_base_value: @get('attached_to_connection_conditional_base_value') + connection_type: 'attached' + new_attached_connection.save() + else if existing_attached_connection # cleanup + if existing_attached_connection.get('isNew') + existing_attached_connection.rollbackAttributes() + else + existing_attached_connection.destroyRecord() + + #@get('working_workflow_action_constraints').invoke 'save' + @incrementProperty 'model.project.update_revision' + @send 'close' + + rollback_and_close: -> + #@set 'select_action_offset_base_alert', '' + @get('working_workflow_action_offsets').invoke 'rollbackAttributes' + @get('working_pointing_workflow_action_connections').invoke 'rollbackAttributes' + #@get('working_workflow_action_constraints').invoke 'rollbackAttributes' + @_super arguments... + + close: -> + Ember.$('body').off('keyup.modal-dialog') + @_super arguments... + set_batch_element_threshold_unit: (unit) -> @set 'model.changeset.batch_element_threshold_unit', unit + positionInProject: -> + @modal 'project/position-workflow-action-definition-in-project', + model: @get('model') + set_offset_type_none: -> + @set 'model.changeset.offset_type', 'none' + set_offset_type_attached: -> + @set 'model.changeset.offset_type', 'attached' + set_offset_type_configured: -> + @set 'model.changeset.offset_type', 'configured' + # Add an initial offset if activated and none are present yet + @send 'add_workflow_action_offset' unless @get('model.workflow_action_offsets.length') + + set_attached_to_workflow_action_workflow_group: (workflow_group) -> + @set 'attached_to_workflow_action_workflow_group', workflow_group + @set 'attached_to_workflow_action', null + @set 'possible_attached_to_workflow_action_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + false + + set_connection_selector_workflow_group: (workflow_action_offset, workflow_group, options = {}) -> + if workflow_action_offset.get('connection_type') is 'attached' + return if workflow_action_offset.get('base_workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'base_workflow_action_definition', null + else + return if workflow_action_offset.get('workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'workflow_action_definition', null + workflow_action_offset.set 'selector_workflow_group', workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + set_selector_workflow_group: (workflow_action_offset, workflow_group, options = {}) -> + return if workflow_action_offset.get('base_workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'base_workflow_action_definition', null + workflow_action_offset.set 'selector_workflow_group', workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + toggle_offset_x_based: (offset, x_based) -> + if x_based + offset.set 'base_workflow_action_definition', null + else + # do not loose original on back and forth toggle + offset.set 'base_workflow_action_definition', offset.get('original_base_workflow_action_definition') + offset.set 'x_based', x_based + false + + set_attached_to_workflow_action: (workflow_action_definition) -> + @set 'attached_to_workflow_action', workflow_action_definition + @set 'attached_to_connection_conditional_base_value', '' + false + #toggle_week_day_presence: (target, week_day, selected) -> + # #target = target.get('batch_element_threshold_business_days') unless Ember.isArray(target) + # if selected + # target.addObject week_day.index + # else + # target.removeObject week_day.index +add_business_day_computations Component, 'new_workflow_action_offset_batch_element_threshold' +export default ModalBase.extend(Component) diff --git a/app/components/workflow-group-edit-constraint.coffee b/app/components/workflow-group-edit-constraint.coffee new file mode 100644 index 0000000..d37b2aa --- /dev/null +++ b/app/components/workflow-group-edit-constraint.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +export default Ember.Component.extend + #tagName: 'h4' + constraint_types: Ember.A(['warn', 'restrict']) + classNames: ['row'] + actions: + destroyConstraint: -> + #@get('constraint').destroyRecord() + @modal 'confirm', + title_path: 'general.destroy.are_you_sure' + model: @get('constraint') + ok: -> + @get('model').destroyRecord() + diff --git a/app/components/workflow-group-header.coffee b/app/components/workflow-group-header.coffee new file mode 100644 index 0000000..0009fb8 --- /dev/null +++ b/app/components/workflow-group-header.coffee @@ -0,0 +1,65 @@ +import Ember from 'ember' +export default Ember.Component.extend + #tagName: 'h4' + classNames: ['workflow-instance-workflow-group-title'] + make_editable: (-> + @set('workflow_group.isEditing', true) + @$('.workflow-group-title-edit').focus() + ).on "doubleClick" + #focusOut: -> @send('saveWorkflowStep') if @get('workflow_group.isEditing') + workflow_group_name: Ember.computed.oneWay 'workflow_group.name' + select_title: (-> + @$('.workflow-group-title-edit').focus() if @get('workflow_group.isEditing') + ).on('didRender') + + actions: + newWorkflowActionDefinition: -> + workflow_group = @get('workflow_group') + #title = "WorkflowActionDefinition #{workflow_group.store.peekAll('panda/workflow-action-definition').get('length') + 1}" + title = "" + if last_workflow_action_definition = workflow_group.get('sorted_workflow_action_definitions.lastObject') + position = 1 + last_workflow_action_definition.get('position') # can be a gap due to deleted actions not updating the rest + else + position = 0 + + workflow_action_definition = @store.createRecord 'panda/workflow_action_definition', + title: title + position: position + workflow_group: workflow_group + project: workflow_group.get('project') + @modal 'workflow-action-definition/edit-workflow-action-definition', model: workflow_action_definition + + saveWorkflowStep: -> + return unless workflow_group_name = @get('workflow_group_name') + workflow_group = @get('workflow_group') + workflow_group.set 'name', workflow_group_name + workflow_group.set 'isEditing', false + workflow_group.save() + rollbackWorkflowStep: -> + #@get('workflow_group').rollbackAttributes() + @set 'workflow_groupName', @get('workflow_group.name') + @set 'workflow_group.isEditing', false + destroyWorkflowStep: -> + @modal 'confirm', + title_path: 'general.destroy.are_you_sure' + model: @get('workflow_group') + ok: -> + workflow_group = @get('model') + workflow_group.rollbackAttributes() + workflow_group.destroyRecord() + close: -> + workflow_group = @get('model') + workflow_group.set "isEditing", false + displayWorkflowActionDefinitions: -> + @modal 'display-workflow-action-definitions', + model: Ember.A([@get('workflow_group')]) + title: @get('workflow_group.name') + + sortWorkflowActionDefinitions: -> + @modal 'sort-records', + model: @get('workflow_group.workflow_action_definitions') + title: "Arrange workflow actions for step #{@get('workflow_group.name')} of project #{@get('workflow_group.project.name')}" + + editWorkflowGroup: -> + @modal 'project/edit-workflow-group', + model: @get('workflow_group') diff --git a/app/controllers/action-list.js b/app/controllers/action-list.js new file mode 100644 index 0000000..2fb8ded --- /dev/null +++ b/app/controllers/action-list.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/controllers/action-list'; diff --git a/app/controllers/organization.coffee b/app/controllers/organization.coffee new file mode 100644 index 0000000..3d53adf --- /dev/null +++ b/app/controllers/organization.coffee @@ -0,0 +1,16 @@ +import Ember from 'ember' +export default Ember.Controller.extend + newProjectName: '' + is_manager: Ember.computed 'model.manager_ids', -> + return false unless manager_ids = @get('model.manager_ids') + return false unless user_id = @get('globals.current_user.id') + manager_ids.includes user_id + + tracker_subjects_for_user: Ember.computed.alias('model.tracker_subjects') + chart_data: [ + ['Year', 'Sales', 'Expenses'] + ['2004', 1000, 400] + ['2005', 1170, 460] + ['2006', 660, 1120] + ['2007', 1030, 540] + ] diff --git a/app/controllers/project.coffee b/app/controllers/project.coffee new file mode 100644 index 0000000..ce67a02 --- /dev/null +++ b/app/controllers/project.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + a: 3 diff --git a/app/controllers/project/index.coffee b/app/controllers/project/index.coffee new file mode 100644 index 0000000..2d25310 --- /dev/null +++ b/app/controllers/project/index.coffee @@ -0,0 +1,15 @@ +import Ember from 'ember' +export default Ember.Controller.extend + is_manager: Ember.computed 'model.manager_ids', -> + return false unless manager_ids = @get('model.manager_ids') + return false unless user_id = @get('globals.current_user.id') + manager_ids.includes user_id + projects_for_user: Ember.computed "model.[]", "model.@each.users.[]", -> + user_id = localStorage.getItem('user_id') + projects = @get('sorted_projects') + return projects if @get('is_manager') # Do not filter, sorry, managers have to see all the projects + projects.filter (project)-> + project.get('users').mapBy('id').includes user_id + #sorted_projects: Ember.computed 'projects.@each.name', -> @get('projects').sortBy('name') + projectSorting: ["name:asc"] + sorted_projects: Ember.computed.sort("model", "projectSorting") diff --git a/app/controllers/project/show/dunlop-generator.coffee b/app/controllers/project/show/dunlop-generator.coffee new file mode 100644 index 0000000..0ce467a --- /dev/null +++ b/app/controllers/project/show/dunlop-generator.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +possible_modules = ['kbt_endpoint', 'wba_mailbox'] +export default Ember.Controller.extend + possible_source_files: ["net_order", "net_task"] + selected_source_files: [] + kbt_endpoint: Ember.Object.create(active: false, config: {}) + wba_mailbox: Ember.Object.create(active: true, config: {}) + activated_modules: Ember.computed "kbt_endpoint.active", "wba_mailbox.active", -> + activated = Ember.A() + for key in possible_modules + activated.push Ember.Object.create(name: key) if @get("#{key}.active") + activated diff --git a/app/controllers/project/show/dunlop.coffee b/app/controllers/project/show/dunlop.coffee new file mode 100644 index 0000000..3a012de --- /dev/null +++ b/app/controllers/project/show/dunlop.coffee @@ -0,0 +1,94 @@ +import Ember from 'ember' +import ActiveWorkflowActionDefinitions from 'ember-panda/mixins/project/active-workflow-action-definitions' +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +export default Ember.Controller.extend + newWorkflowStepName: '' + batch_size_simulation_range: Ember.computed 'model.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('model.batch_size_lower_value'), max: @get('model.batch_size_upper_value')} + batch_size_simulation_value: 0 + chosen_absolute_date: null + reference_date_day_of_week: Ember.computed.oneWay 'model.reference_date_day_of_week' + project: Ember.computed.alias 'model' + + # Multiple project scenarios (currently inactive) + selected_project_scenarios: [] + has_multiple_selected_project_scenarios: Ember.computed.gt 'selected_project_scenarios.length', 1 + has_one_selected_project_scenario: Ember.computed.equal 'selected_project_scenarios.length', 1 + selected_project_scenarios_set_operation: 'union' + + active_project_scenario: null + + init: -> + @_super arguments... + #@get 'active_workflow_action_definitions_identity' + #@set_active_workflow_action_definitions() + + #_active_workflow_action_definitions_obsever: Ember.observer 'selected_project_scenarios.@each.id', 'selected_project_scenarios_set_operation', 'project.workflow_action_definitions.@each.id', 'project.workflow_groups.@each.id', 'project.update_revision', -> + _active_workflow_action_definitions_obsever: Ember.observer 'active_project_scenario', 'project.workflow_action_definitions.@each.id', 'project.workflow_groups.@each.id', 'project.update_revision', -> + Ember.run.debounce @, (-> + #[project_scenarios, set_operation] = @getPropertyValues('selected_project_scenarios', 'selected_project_scenarios_set_operation') + set_operation = 'union' + if active_project_scenario = @get('active_project_scenario') + @set 'active_workflow_action_definitions', get_active_workflow_action_definitions(@get('model.workflow_action_definitions'), [active_project_scenario], set_operation) + @set 'active_workflow_groups', get_active_workflow_groups(@get('model.workflow_groups'), [active_project_scenario], set_operation) + else + @set 'active_workflow_action_definitions', @get('model.workflow_action_definitions') + @set 'active_workflow_groups', @get('model.workflow_groups') + ), 350 + + actions: + newWorkflowStep: -> + new_name = @get('newWorkflowStepName') + workflow_group = @get("model.workflow_groups").createRecord(name: new_name, position: @get('model.workflow_groups.length')) + @set 'newWorkflowStepName', '' + @modal "project/edit-workflow-group", + model: workflow_group + destroyWorkflowStep: (workflow_group) -> workflow_group.destroyRecord() + toggle_sidebar: (id) -> + $("##{id}").sidebar('toggle') + positionWorkflowActionDefinition: (dragged_workflow_action_definition, options = {})-> + workflow_group = options.target.content + items = options.target.$('.draggableItem') + top_offsets = items.map (i, item) -> $(item).offset().top + dragged_offset = options.event.originalEvent.clientY + my_position = 0 + compare_offset = top_offsets[0] + while compare_offset and compare_offset < dragged_offset + my_position += 1 + compare_offset = top_offsets[my_position] + position_counter = 0 + workflow_group.get('sorted_workflow_action_definitions').forEach (workflow_action_definition)-> + if position_counter is my_position + position_counter += 1 + workflow_action_definition.set 'position', position_counter + workflow_action_definition.save() + position_counter += 1 + # Just before the last one, so the + + dragged_workflow_action_definition.set 'workflow_group', workflow_group + dragged_workflow_action_definition.set 'position', my_position + dragged_workflow_action_definition.save() + displayWorkflowActionDefinitions: -> + #workflow_action_definitions = Ember.A() + #@get('model.sorted_workflow_groups').forEach (workflow_group, i)-> + # workflow_action_definitions.addObjects(workflow_group.get('workflow_action_definitions')) + @modal 'display-workflow-action-definitions', + #model: workflow_action_definitions + model: @get('model.sorted_workflow_groups') + title: @get('model.name') + show_project_info: -> + @modal 'project/show/dunlop-project-info', model: @get('model') + sortWorkflowSteps: -> + @modal 'sort-records', + model: @get('model.workflow_groups') + title: "Sort workflow steps for project #{@get('model.name')}" + + positionWorkflowActionDefinitionInProject: (workflow_action_definition) -> + @modal 'project/position-workflow-action-definition-in-project', + model: workflow_action_definition + + select_project_scenario: (project_scenario) -> + @set 'selected_project_scenarios', project_scenario + + diff --git a/app/controllers/project/show/index.coffee b/app/controllers/project/show/index.coffee new file mode 100644 index 0000000..7821103 --- /dev/null +++ b/app/controllers/project/show/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects: (-> @store.peekAll('panda/tracker-subject')).property() diff --git a/app/controllers/project/show/project-instance-action/index.coffee b/app/controllers/project/show/project-instance-action/index.coffee new file mode 100644 index 0000000..7844d9d --- /dev/null +++ b/app/controllers/project/show/project-instance-action/index.coffee @@ -0,0 +1,89 @@ +import Ember from 'ember' +import {create_date_sort} from 'ember-cli-dunlop/utils/date-sort' +date_sort = create_date_sort('date') +export default Ember.Controller.extend + #filter: Ember.Object.create + # workflow_action_definition_id_in: [] + # project_instance_group_workflow_group_id_in: [] + # state_in: [] + # date_gteq: null + # date_lteq: null + filtered_project_instance_actions_inline: Ember.computed 'project.project_instance_actions.@each.id', 'filter.{workflow_action_definition_id_in.length,state_in.length,project_instance_group_workflow_group_id_in.length,date_gteq,date_lteq}', -> + base = @get('project.project_instance_actions').toArray() + if @get('filter.workflow_action_definition_id_in.length') + base = base.filter (record) => record.get('workflow_action_definition.id') in @get('filter.workflow_action_definition_id_in') + if @get('filter.project_instance_group_workflow_group_id_in.length') + base = base.filter (record) => record.get('project_instance_group.workflow_group.id') in @get('filter.project_instance_group_workflow_group_id_in') + if date_gteq = @get('filter.date_gteq') + base = base.filter (record) -> if date = record.get('date') then date.toISOString().substring(0, 10) >= date_gteq.toISOString().substring(0, 10) else false + if date_lteq = @get('filter.date_lteq') + base = base.filter (record) -> if date = record.get('date') then date.toISOString().substring(0, 10) <= date_lteq.toISOString().substring(0, 10) else false + if @get('filter.state_in.length') + base = base.filter (record) => record.get('state') in @get('filter.state_in') + base.sort date_sort + filtered_project_instance_actions: Ember.computed.alias 'model' + + #date_filter_active: Ember.computed.or 'filter.{date_gteq,date_lteq}' + #date_filter_text: Ember.computed 'filter.date_gteq', 'filter.date_lteq', -> + # if gteq = @get('filter.date_gteq') + # gteq = gteq.format('ll') + # else + # gteq = '-' + # if lteq = @get('filter.date_lteq') + # lteq = lteq.format('ll') + # else + # lteq = '-' + # "#{gteq} / #{lteq}" + + filter_workflow_action_definitions: Ember.computed 'filters.workflow_action_definition_id_in.[]', -> + @get('project.workflow_action_definitions').filter((r) => r.get('id') in @get('filters.workflow_action_definition_id_in')) + workflow_action_definitions_filter_html: Ember.computed 'filters.workflow_action_definition_id_in.[]', -> + length = @get('filters.workflow_action_definition_id_in.length') + return '' unless length + tooltip = @get('project.workflow_action_definitions').filter((r) => r.get('id') in @get('filters.workflow_action_definition_id_in')).mapBy('title').join("<br>") + model_name = if length is 1 then t('models.workflow_action_definition') else t('models.plural.workflow_action_definition') + "#{length} #{model_name}" + + actions: + toggle_filter: (key, value, checked) -> + new_value = [] + if checked + new_value = new_value.concat(@get("filters.#{key}")).concat(value) + else + @get("filters.#{key}").forEach (present_in_filter) -> + new_value.push present_in_filter unless present_in_filter is value + @set "filters.#{key}", new_value + query = @get('filters')?.toProperties() || {} + @set('model.page', 1) + @set('model.lastPage', null) + @get('model').setOtherParam('q', query) + false + + #toggle_filter: (key, value, checked) -> + # if checked + # @get("filters.#{key}").addObject value + # else + # @get("filters.#{key}").removeObject value + # query = @get('filters')?.toProperties() || {} + # @get('model').setOtherParam('q', query) + clear_filter: (keys...) -> + keys.forEach (key) => + if Ember.isArray(@get("filters.#{key}")) + @set "filters.#{key}", [] + else + @set "filters.#{key}", null + query = @get('filters')?.toProperties() || {} + @set('model.page', 1) + @set('model.lastPage', null) + @get('model').setOtherParam('q', query) + false + #set_filter_date_today: -> + # today = moment() + # @setProperties + # 'filters.target_date_gteq': today + # 'filters.target_date_lteq': today + #set_filter_date_this_week: -> + # today = moment() + # @setProperties + # 'filters.target_date_gteq': today.clone().startOf('isoWeek') + # 'filters.target_date_lteq': today.endOf('isoWeek') diff --git a/app/controllers/project/show/project-instance/index.coffee b/app/controllers/project/show/project-instance/index.coffee new file mode 100644 index 0000000..69eddfc --- /dev/null +++ b/app/controllers/project/show/project-instance/index.coffee @@ -0,0 +1,47 @@ +import Ember from 'ember' +import {create_date_sort} from 'ember-cli-dunlop/utils/date-sort' +{get} = Ember +date_sort = create_date_sort('reference_date') +export default Ember.Controller.extend + possible_states: ['uncategorized', 'unplanned', 'planned', 'active', 'overdue', 'any_rejected', 'all_completed'] + #filter: Ember.Object.create + # project_scenario_id_in: [] + # state_in: [] + # reference_date_gteq: null + # reference_date_lteq: null + #filter_project_scenarios: Ember.computed 'filter.project_scenario_id_in.[]', -> + # @get('model.sorted_project_scenarios').filter((r) => r.get('id') in @get('filter.project_scenario_id_in')) + #reference_date_filter_active: Ember.computed.or 'filter.{reference_date_gteq,reference_date_lteq}' + #reference_date_filter_text: Ember.computed 'filter.reference_date_gteq', 'filter.reference_date_lteq', -> + # if gteq = @get('filter.reference_date_gteq') + # gteq = gteq.format('ll') + # else + # gteq = '-' + # if lteq = @get('filter.reference_date_lteq') + # lteq = lteq.format('ll') + # else + # lteq = '-' + # "#{gteq} / #{lteq}" + #filtered_project_instances: Ember.computed 'model.project_instances.@each.id', 'filter.{project_scenario_id_in.length,state_in.length,reference_date_gteq,reference_date_lteq}', -> + # base = @get('model.project_instances').toArray() + # if @get('filter.project_scenario_id_in.length') + # base = base.filter (record) => record.get('project_scenario.id') in @get('filter.project_scenario_id_in') + # if reference_date_gteq = @get('filter.reference_date_gteq') + # base = base.filter (record) -> if reference_date = record.get('reference_date') then reference_date.toISOString().substring(0, 10) >= reference_date_gteq.toISOString().substring(0, 10) else false + # if reference_date_lteq = @get('filter.reference_date_lteq') + # base = base.filter (record) -> if reference_date = record.get('reference_date') then reference_date.toISOString().substring(0, 10) <= reference_date_lteq.toISOString().substring(0, 10) else false + # if @get('filter.state_in.length') + # base = base.filter (record) => record.get('state') in @get('filter.state_in') + # base.sort date_sort + filtered_project_instances: Ember.computed.alias 'model' + actions: + toggle_filter: (key, value, checked) -> + if checked + @get("filter.#{key}").addObject value + else + @get("filter.#{key}").removeObject value + clear_filter: (key) -> + if Ember.isArray(@get("filter.#{key}")) + @set "filter.#{key}", [] + else + @set "filter.#{key}", null diff --git a/app/controllers/project/show/project-instance/show.coffee b/app/controllers/project/show/project-instance/show.coffee new file mode 100644 index 0000000..71e0a8f --- /dev/null +++ b/app/controllers/project/show/project-instance/show.coffee @@ -0,0 +1,11 @@ +import Ember from 'ember' +{get} = Ember +export default Ember.Controller.extend + breadcrumb_detail: Ember.computed 'globals.current_route.path', -> + return 'grid??' unless currentPath = @get('globals.current_route.path') + route_specific = currentPath.split('.')[4] + switch route_specific + when 'index' then t('project_instance.show.grid') + when 'groups' then t('models.plural.project_instance_group') + when 'actions' then t('models.plural.workflow_action_definition') + else t("project_instance.show.#{route_specific}") diff --git a/app/controllers/project/show/tracker-subject.coffee b/app/controllers/project/show/tracker-subject.coffee new file mode 100644 index 0000000..ca27665 --- /dev/null +++ b/app/controllers/project/show/tracker-subject.coffee @@ -0,0 +1,53 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups: [] + uncategorized_workflow_groups: [] + #grouped_tracker_groups: Ember.computed.alias("model.tracker_subject.sorted_tracker_groups") + + #grouped_tracker_groups_setter: (-> + # base = @ + # group = Ember.A() + # project_workflow_groups = @get('model.project.workflow_groups') + # @get("model.tracker_subject.tracker_groups").then (tracker_groups)-> + # tracker_groups.forEach (tracker_group)-> + # item_group = Ember.Object.extend( + # workflow_groups: Ember.computed.intersect("model.workflow_groups", "project_workflow_groups") + # ).create + # model: tracker_group + # project_workflow_groups: project_workflow_groups + # c = base + # debugger + # #item_group = Ember.Object.create + # # model: tracker_group + # # workflow_groups: workflow_groups.filterBy("") + # #tracker_group.get('workflow_groups').then (workflow_groups)-> + # # workflow_groups.forEach (workflow_group)-> + # # hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + # # if hasProject + # # existing_project_group.get("workflow_groups").push(workflow_group) + # # else + # # item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + # # base.incrementProperty "change_iteration" + # group.push item_group + # @set("grouped_tracker_groups", group) + #).observes "model.tracker_subject.tracker_groups.@each.workflow_groups.[]" + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + tracker_subject_id = @get("model.tracker_subject.id") + categorized_workflow_group_ids = [] + @get("model.tracker_subject.tracker_groups").forEach (tracker_group)-> + group.pushObject + model: tracker_group + workflow_groups: Ember.A() + @get('model.project.workflow_groups').forEach (workflow_group)-> + return unless project_tracker_group = workflow_group.get('tracker_groups').findBy("tracker_subject.id", tracker_subject_id) + return unless item_group = group.findBy("model.id", project_tracker_group.id) + categorized_workflow_group_ids.push workflow_group.id + item_group.workflow_groups.pushObject workflow_group + item_group.workflow_groups = item_group.workflow_groups.sortBy("position") + @set("grouped_tracker_groups", group) + uncategorized_workflow_groups = @get('model.project.workflow_groups').reject((ws) -> categorized_workflow_group_ids.includes(ws.id)) + @set("uncategorized_workflow_groups", uncategorized_workflow_groups.sortBy("position")) + ).observes "model.project.workflow_groups.@each.tracker_groups.[]", "model.tracker_subject.tracker_groups.[]" diff --git a/app/controllers/project/show/tracker-subjects.coffee b/app/controllers/project/show/tracker-subjects.coffee new file mode 100644 index 0000000..337e8fb --- /dev/null +++ b/app/controllers/project/show/tracker-subjects.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects_for_project: (-> @store.peekAll('panda/tracker-subject')).property() diff --git a/app/controllers/tracker-subject/index.coffee b/app/controllers/tracker-subject/index.coffee new file mode 100644 index 0000000..401c3d5 --- /dev/null +++ b/app/controllers/tracker-subject/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects_for_user: Ember.computed.alias 'model' diff --git a/app/controllers/tracker-subject/show/index.coffee b/app/controllers/tracker-subject/show/index.coffee new file mode 100644 index 0000000..aad2a10 --- /dev/null +++ b/app/controllers/tracker-subject/show/index.coffee @@ -0,0 +1,22 @@ +import Ember from 'ember' +export default Ember.Controller.extend + actions: + sortTrackerGroups: -> + @modal "sort-records", model: @get("model.tracker_groups") + + newTrackerGroup: -> + tracker_groups = @get("model.tracker_groups") + @modal "edit-tracker-group", + model: tracker_groups.createRecord(position: tracker_groups.get('length')) + title: t('action.new_model', model: t('models.tracker_group')) + + editTrackerGroup: (tracker_group) -> + @modal "edit-tracker-group", model: tracker_group + + newTrackerItem: (tracker_group)-> + tracker_items = tracker_group.get("tracker_items") + tracker_items.createRecord(position: tracker_items.get("length"), isEditing: true) + + sortTrackerItems: (tracker_group) -> + @modal "sort-records", model: tracker_group.get("tracker_items") + diff --git a/app/controllers/tracker-subject/show/project.coffee b/app/controllers/tracker-subject/show/project.coffee new file mode 100644 index 0000000..1e099ae --- /dev/null +++ b/app/controllers/tracker-subject/show/project.coffee @@ -0,0 +1,54 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups: Ember.A() + uncategorized_workflow_groups: Ember.A() + #grouped_tracker_groups: Ember.computed.alias("model.tracker_subject.sorted_tracker_groups") + + #grouped_tracker_groups_setter: (-> + # base = @ + # group = Ember.A() + # project_workflow_groups = @get('model.project.workflow_groups') + # @get("model.tracker_subject.tracker_groups").then (tracker_groups)-> + # tracker_groups.forEach (tracker_group)-> + # item_group = Ember.Object.extend( + # workflow_groups: Ember.computed.intersect("model.workflow_groups", "project_workflow_groups") + # ).create + # model: tracker_group + # project_workflow_groups: project_workflow_groups + # c = base + # debugger + # #item_group = Ember.Object.create + # # model: tracker_group + # # workflow_groups: workflow_groups.filterBy("") + # #tracker_group.get('workflow_groups').then (workflow_groups)-> + # # workflow_groups.forEach (workflow_group)-> + # # hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + # # if hasProject + # # existing_project_group.get("workflow_groups").push(workflow_group) + # # else + # # item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + # # base.incrementProperty "change_iteration" + # group.push item_group + # @set("grouped_tracker_groups", group) + #).observes "model.tracker_subject.tracker_groups.@each.workflow_groups.[]" + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + tracker_subject_id = @get("model.tracker_subject.id") + categorized_workflow_group_ids = [] + @get("model.tracker_subject.tracker_groups").forEach (tracker_group) -> + group.pushObject + model: tracker_group + workflow_groups: Ember.A() + + @get('model.project.workflow_groups').forEach (workflow_group)-> + return unless project_tracker_group = workflow_group.get('tracker_groups').findBy("tracker_subject.id", tracker_subject_id) + return unless item_group = group.findBy("model.id", project_tracker_group.id) + categorized_workflow_group_ids.push workflow_group.id + item_group.workflow_groups.pushObject workflow_group + item_group.workflow_groups = item_group.workflow_groups.sortBy("position") + @set("grouped_tracker_groups", group) + uncategorized_workflow_groups = @get('model.project.workflow_groups').reject((ws) -> categorized_workflow_group_ids.includes(ws.id)) + @set("uncategorized_workflow_groups", uncategorized_workflow_groups.sortBy("position")) + ).observes "model.project.workflow_groups.@each.tracker_groups.[]", "model.tracker_subject.tracker_groups.[]" diff --git a/app/controllers/tracker-subject/show/tracker-group.coffee b/app/controllers/tracker-subject/show/tracker-group.coffee new file mode 100644 index 0000000..04cd8ce --- /dev/null +++ b/app/controllers/tracker-subject/show/tracker-group.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups_setter: (-> + project_groups = Ember.A() + @get("model.workflow_groups").then (workflow_groups) -> + workflow_groups.forEach (workflow_group) -> + if existing_group = project_groups.findBy("model.id", workflow_group.get("project.id")) + existing_group.get("workflow_groups").pushObject(workflow_group) + else + project_groups.push Ember.Object.create + model: workflow_group.get('project') + workflow_groups: Ember.A([workflow_group]) + @set("grouped_projects", project_groups) + ).observes("model.workflow_groups.[]") diff --git a/app/controllers/tracker-subject/show/tracking-connections.coffee b/app/controllers/tracker-subject/show/tracking-connections.coffee new file mode 100644 index 0000000..93045ce --- /dev/null +++ b/app/controllers/tracker-subject/show/tracking-connections.coffee @@ -0,0 +1,64 @@ +import Ember from 'ember' +import Viz from 'npm:viz.js' # browserify +export default Ember.Controller.extend + change_iteration: 0 + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + @get("model.tracker_groups").then (tracker_groups)-> + tracker_groups.forEach (tracker_group)-> + item_group = Ember.Object.create + model: tracker_group + projects: Ember.A() + tracker_group.get('workflow_groups').then (workflow_groups)-> + workflow_groups.forEach (workflow_group)-> + hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + if hasProject + existing_project_group.get("workflow_groups").push(workflow_group) + else + item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + base.incrementProperty "change_iteration" + group.push item_group + @set("grouped_tracker_groups", group) + ).observes "model.tracker_groups.@each.workflow_groups.@each.project" + + #graph_content: Ember.computed "grouped_tracker_groups", -> + #graph_content: Ember.computed "model.tracker_groups.@each.workflow_groups.@each.project", -> + graph_content: Ember.computed "change_iteration", -> + txt = "digraph prof {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\"];\n rankdir=TB;\n" + labeled_projects = {} + item_projects = {} + tracker_group_color = '"#AAAAAA"' + project_color = '"#AAFFAA"' + workflow_group_color = '"#AAAAFF"' + + grouped_tracker_groups = @get("grouped_tracker_groups") + grouped_tracker_groups.forEach (tracker_group_group)-> + tracker_group_id = "q#{tracker_group_group.get("model.id")}" # start with a letter + txt += " #{tracker_group_id} [label=\"#{tracker_group_group.get('model.name')}\" fillcolor=#{tracker_group_color}];\n" + tracker_group_group.get("projects").forEach (project)-> + project_id = "q#{project.get("model.id")}" + unless labeled_projects[project_id] + txt += " #{project_id} [label=\"#{project.get('model.name')}\" fillcolor=#{project_color}];\n" + labeled_projects[project_id] = 1 + project.workflow_groups.forEach (workflow_group)-> + workflow_group_id = "q#{workflow_group.id}" + txt += " #{workflow_group_id} [label=\"#{workflow_group.get('name')}\" fillcolor=#{workflow_group_color}];\n" + + unless item_projects["#{tracker_group_id}-#{project_id}"] + txt += " #{tracker_group_id} -> #{project_id} [color=\"#AAAAAA\" penwidth=1];\n" + item_projects["#{tracker_group_id}-#{project_id}"] = 1 + txt += " #{project_id } -> #{workflow_group_id} [color=\"#AAAAAA\" penwidth=1];\n" + #txt += " #{tracker_group_id} -> #{project_id} -> #{workflow_group_id};\n" + + txt += "}" + txt + svg_content: Ember.computed 'model.id', 'graph_content', -> + data = @get('graph_content') + try + svg = Viz(data, format: 'svg') + svg.htmlSafe() + catch error + console.log "Error parsing\n\n#{data}" + diff --git a/app/controllers/user/show.coffee b/app/controllers/user/show.coffee new file mode 100644 index 0000000..dfa9051 --- /dev/null +++ b/app/controllers/user/show.coffee @@ -0,0 +1,24 @@ +import Ember from 'ember' +export default Ember.Controller.extend + adapters: [] + panda_authorizations: [] # set by route + + active_authorizations: [] + + actions: + set_permission: (authorization, permission) -> + new_roles = [] + @get('active_authorizations').forEach (role) -> + if role.match(new RegExp("-#{authorization.type}-#{authorization.target}$")) + new_roles.push "#{permission}-#{authorization.type}-#{authorization.target}" + else + # different authorization + new_roles.push role + @set 'active_authorizations', new_roles + save_permissions: -> + user_roles = [] + @get('active_authorizations').forEach (role) -> + user_roles.push role unless role.match(/^ban-/) + user = @get('model') + user.set 'role_names', user_roles + user.save().then => @set 'globals.flash_message', 'Permissions saved' diff --git a/app/helpers/can.coffee b/app/helpers/can.coffee new file mode 100644 index 0000000..25031eb --- /dev/null +++ b/app/helpers/can.coffee @@ -0,0 +1,7 @@ +import Ember from 'ember' +import DunlopCan from 'ember-cli-dunlop/helpers/dunlop-can' + +export default DunlopCan.extend + custom_compute: (permission, type, target, roles) -> + return true if 'manage-app-panda' in roles + false diff --git a/app/helpers/date.coffee b/app/helpers/date.coffee new file mode 100644 index 0000000..6b9d40f --- /dev/null +++ b/app/helpers/date.coffee @@ -0,0 +1,15 @@ +import Ember from 'ember' + +# This function receives the params `params, hash` +date = ([date], options={}) -> + return '' unless date + return date if typeof date is 'string' + date = moment(date) unless date._isAMomentObject + date.format(options.format || 'll') + +DateHelper = Ember.Helper.helper date + +export { date } + +export default DateHelper + diff --git a/app/helpers/dynamic-path-t.coffee b/app/helpers/dynamic-path-t.coffee new file mode 100644 index 0000000..330b376 --- /dev/null +++ b/app/helpers/dynamic-path-t.coffee @@ -0,0 +1,7 @@ +import Ember from 'ember' +export default Ember.Helper.helper ([path_part, rest...], options)-> + toptions = options + path = "#{path_part}.#{options.postpath}" + text = t(path, toptions) + tag = if options.bare then text else "#{text}" + tag.htmlSafe() diff --git a/app/helpers/l.coffee b/app/helpers/l.coffee new file mode 100644 index 0000000..50b2468 --- /dev/null +++ b/app/helpers/l.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' + +# This function receives the params `params, hash` +l = ([date]) -> + return '' unless date + return date if typeof date is 'string' + return date.format('ll') if date._isAMomentObject + date.toISOString().substring(0,10) + +LHelper = Ember.Helper.helper l + +export { l } + +export default LHelper diff --git a/app/models/panda/action-event.js b/app/models/panda/action-event.js new file mode 100644 index 0000000..1f4b7d2 --- /dev/null +++ b/app/models/panda/action-event.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/action-event'; diff --git a/app/models/panda/adapter.coffee b/app/models/panda/adapter.coffee new file mode 100644 index 0000000..77c37cb --- /dev/null +++ b/app/models/panda/adapter.coffee @@ -0,0 +1,6 @@ +import DS from 'ember-data' + +export default DS.Model.extend + title: DS.attr('string') + authorizations: DS.attr(defaultValue: -> []) + diff --git a/app/models/panda/organization.coffee b/app/models/panda/organization.coffee new file mode 100644 index 0000000..f86b084 --- /dev/null +++ b/app/models/panda/organization.coffee @@ -0,0 +1,40 @@ +import DS from 'ember-data' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/organization' +validator = lookupValidator(ModelValidations) +import config from 'ember-get-config' + +export default DS.Model.extend + name: DS.attr('string') + logo: DS.attr() + changeset: (-> + new Changeset @, validator, ModelValidations + ).property() + + #TODO: Move as general implementation to ember-cli-dunlop + api_url: Ember.computed 'id', -> + return unless id = @get('id') + modelName = @get('constructor.modelName') + adapter = @store.adapterFor(modelName) + adapter.buildURL modelName, id + action_url: (action) -> + return unless api_url = @get('api_url') + "#{api_url}/#{action}" + + logo_url: Ember.computed 'logo', -> + urls = {} + return urls unless logo = @get('logo') + for key, relative_url of logo + urls[key] = "#{config.apiHost}#{relative_url}" + urls + + #projects: DS.hasMany('project', inverse: 'organizations') + manager_ids: DS.attr(defaultValue: -> []) + isEditing: DS.attr('boolean', defaultValue: false) + + users: DS.hasMany('user', serialize: 'ids', inverse: 'organizations') + tracker_subjects: DS.hasMany('panda/tracker-subject') + #workflow_instance_types: DS.hasMany("workflow-instance-type") + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'organization') + diff --git a/app/models/panda/project-instance-action-connection.js b/app/models/panda/project-instance-action-connection.js new file mode 100644 index 0000000..cf9855c --- /dev/null +++ b/app/models/panda/project-instance-action-connection.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/project-instance-action-connection'; diff --git a/app/models/panda/project-instance-action.coffee b/app/models/panda/project-instance-action.coffee new file mode 100644 index 0000000..0c8358c --- /dev/null +++ b/app/models/panda/project-instance-action.coffee @@ -0,0 +1,49 @@ +import DS from 'ember-data' +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'pending') # pending, planned, (active), overdue, rejected, completed + pending_at: DS.attr('moment') + planned_at: DS.attr('moment') + overdue_at: DS.attr('moment') + inquiry_at: DS.attr('moment') + rejected_at: DS.attr('moment') + completed_at: DS.attr('moment') + target_date: DS.attr('moment') + target_date_lock: DS.attr('boolean', defaultValue: false) + + string_value: DS.attr('string', defaultValue: '') + text_value: DS.attr('string', defaultValue: '') + date_value: DS.attr('moment') + timestamp_value: DS.attr('moment') + number_value: DS.attr('number') + boolean_value: DS.attr('boolean', defaultValue: false) + #date, type: Date + notes: DS.attr('string') + + completed: Ember.computed.equal 'state', 'completed' + + value_display: Ember.computed 'workflow_action_definition.value_type', -> + return '' unless value_type = @get('workflow_action_definition.value_type') + switch value_type + when 'string', 'select' + @get('string_value') + when 'number' + @get('number_value') + when 'text' + @get('text_value') + when 'date' + @get('date_value')?.format('LL') + when 'timestamp' + @get('timestamp_value')?.format('LLL') + when 'boolean' + cls = if @get('boolean_value') then 'checkmark box' else 'square outline' + "".htmlSafe() + + project: DS.belongsTo('panda/project', inverse: 'project_instance_actions') + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'project_instance_actions') + project_instance_group: DS.belongsTo('panda/project-instance-group', inverse: 'project_instance_actions') + project_instance: DS.belongsTo('panda/project-instance', inverse: 'project_instance_actions') + + #project_instance_action_connections: DS.hasMany('panda/project-instance-action-connection') # use for workflow, not actual instance handling + pointing_project_instance_action_connections: DS.hasMany('panda/project-instance-action-connection', inverse: 'base_project_instance_action') + + events: DS.hasMany('panda/action-event', inverse: 'project_instance_action') diff --git a/app/models/panda/project-instance-group.coffee b/app/models/panda/project-instance-group.coffee new file mode 100644 index 0000000..7d77189 --- /dev/null +++ b/app/models/panda/project-instance-group.coffee @@ -0,0 +1,10 @@ +import DS from 'ember-data' + +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'pending') + notes: DS.attr('string') + + project: DS.belongsTo('panda/project', inverse: 'project_instance_groups') + project_instance: DS.belongsTo('panda/project-instance', inverse: 'project_instance_groups') + workflow_group: DS.belongsTo('panda/workflow-group') + project_instance_actions: DS.hasMany( 'panda/project-instance-action', inverse: 'project_instance_group' ) diff --git a/app/models/panda/project-instance.coffee b/app/models/panda/project-instance.coffee new file mode 100644 index 0000000..f891fb7 --- /dev/null +++ b/app/models/panda/project-instance.coffee @@ -0,0 +1,14 @@ +import DS from 'ember-data' + +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'uncategorized') + client_identifier: DS.attr('string') + reference_date: DS.attr('moment') + batch_size: DS.attr('number') + notes: DS.attr('string') + + project: DS.belongsTo('panda/project', inverse: 'project_instances') + project_scenario: DS.belongsTo('panda/project-scenario', inverse: 'project_instances') + project_instance_groups: DS.hasMany('panda/project-instance-group', inverse: 'project_instance') + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'project_instance') + diff --git a/app/models/panda/project-scenario.coffee b/app/models/panda/project-scenario.coffee new file mode 100644 index 0000000..1c02f84 --- /dev/null +++ b/app/models/panda/project-scenario.coffee @@ -0,0 +1,11 @@ +import DS from 'ember-data' + +export default DS.Model.extend + identifier: DS.attr('string') + name: DS.attr('string') + project: DS.belongsTo('panda/project', inverse: 'project_scenarios') + + workflow_groups: DS.hasMany('panda/workflow-group', serialize: 'ids') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'project_scenarios', serialize: 'ids') + + project_instances: DS.hasMany('panda/project-instance', inverse: 'project_scenario') diff --git a/app/models/panda/project.coffee b/app/models/panda/project.coffee new file mode 100644 index 0000000..d7bc19c --- /dev/null +++ b/app/models/panda/project.coffee @@ -0,0 +1,66 @@ +import DS from 'ember-data' +export default DS.Model.extend + update_revision: 0 + + manager_ids: DS.attr(defaultValue: -> []) + name: DS.attr('string') + identifier: DS.attr('string') + start_date: DS.attr('moment') + end_date: DS.attr('moment') + percent_done: DS.attr('number', defaultValue: 0) + expected_number_of_workflow_instances: DS.attr('number') + instance_scope: DS.attr('string', defaultValue: 'instance') + batch_size_lower_value: DS.attr('number', defaultValue: 0) + batch_size_upper_value: DS.attr('number', defaultValue: 200) + reference_date_day_of_week: DS.attr('string') + + #organizations: DS.hasMany('organization', inverse: 'projects', serialize: 'ids') + workflow_groups: DS.hasMany('panda/workflow-group') + users: DS.hasMany('user', serialize: 'ids') + workflow_instance_type: DS.belongsTo('panda/workflow-instance-type', inverse: 'projects') + tracker_subjects: DS.hasMany('panda/tracker-subject') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'project') + project_scenarios: DS.hasMany('panda/project-scenario', inverse: 'project') + + workflowGroupSort: ['position:asc'] + sorted_workflow_groups: Ember.computed.sort('workflow_groups', 'workflowGroupSort') + sorted_workflow_action_definitions: Ember.computed 'workflow_groups.@each.position', 'workflow_action_definitions.@each.position', -> + @get('workflow_action_definitions').map((r) -> + base = r.get('workflow_group.position') || 100000 + r.set('absolute_sorter', base + r.get('position')) + r + ).sortBy 'absolute_sorter' + unassigned_workflow_action_definitions: Ember.computed 'workflow_action_definitions.@each.workflow_group.id', -> + @get('workflow_action_definitions').rejectBy('workflow_group.id').sortBy 'title' + + instance_scope_batch: Ember.computed.equal 'instance_scope', 'batch' + + project_instances: DS.hasMany('panda/project-instance', inverse: 'project') + project_instance_groups: DS.hasMany('panda/project-instance-group', inverse: 'project') + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'project') + + project_scenarios_sort: ['name:asc'] + sorted_project_scenarios: Ember.computed.sort 'project_scenarios', 'project_scenarios_sort' + #unassigned_workflow_action_definitions: Ember.computed -> + # promise = new Ember.RSVP.Promise (resolve) => + # @get('workflow_action_definitions').then (workflow_action_definitions) -> + # Ember.RSVP.all(workflow_action_definitions.mapBy 'workflow_group').then -> + # resolve workflow_action_definitions.sortBy 'title' + # DS.PromiseArray.create + # promise: promise + #unassigned_workflow_action_definitions: ( -> + # [@store.createRecord('panda/workflow-action-definition', id: 'asdf', title: 'asdf2')] + #).property() + #unassigned_workflow_action_definitions: Ember.computed -> + # promise = @get('workflow_action_definitions') + # promise.then (workflow_action_definitions) -> + # workflow_action_definitions.rejectBy('workflow_group').sortBy 'title' + # promise + #unassigned_workflow_action_definitions: Ember.computed -> + # DS.PromiseArray.create + # promise: Ember.RSVP.filter @get('workflow_action_definitions'), (workflow_action_definitions) -> + # debugger + # workflow_action_definitions.sortBy 'title' + + #@get('workflow_action_definitions').rejectBy('workflow_group.id').sortBy 'title' + diff --git a/app/models/panda/tracker-group.coffee b/app/models/panda/tracker-group.coffee new file mode 100644 index 0000000..362c5ce --- /dev/null +++ b/app/models/panda/tracker-group.coffee @@ -0,0 +1,12 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + position: DS.attr('number') + tracker_subject: DS.belongsTo('panda/tracker-subject') + tracker_items: DS.hasMany('panda/tracker-item') + workflow_groups: DS.hasMany('panda/workflow-group') + + trackerItemSort: ['position:asc'] + #sorted_tracker_items: Ember.computed.sort('tracker_items', 'trackerItemSort') + sorted_tracker_items: Ember.computed 'tracker_items.@each.position', -> + @get('tracker_items').sortBy('position') diff --git a/app/models/panda/tracker-item.coffee b/app/models/panda/tracker-item.coffee new file mode 100644 index 0000000..536e7bb --- /dev/null +++ b/app/models/panda/tracker-item.coffee @@ -0,0 +1,5 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + position: DS.attr('number') + tracker_group: DS.belongsTo('tracker-group') diff --git a/app/models/panda/tracker-subject.coffee b/app/models/panda/tracker-subject.coffee new file mode 100644 index 0000000..99de897 --- /dev/null +++ b/app/models/panda/tracker-subject.coffee @@ -0,0 +1,11 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + edit_from_workflow_group: DS.attr('boolean') + + organization: DS.belongsTo('panda/organization') + tracker_groups: DS.hasMany('panda/tracker-group') + projects: DS.hasMany('panda/project', serialize: 'ids') + + trackerGroupSort: ['position:asc'] + sorted_tracker_groups: Ember.computed.sort('tracker_groups', 'trackerGroupSort') diff --git a/app/models/panda/workflow-action-connection.js b/app/models/panda/workflow-action-connection.js new file mode 100644 index 0000000..5d86aa5 --- /dev/null +++ b/app/models/panda/workflow-action-connection.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/workflow-action-connection'; diff --git a/app/models/panda/workflow-action-constraint.coffee b/app/models/panda/workflow-action-constraint.coffee new file mode 100644 index 0000000..7c41d44 --- /dev/null +++ b/app/models/panda/workflow-action-constraint.coffee @@ -0,0 +1,9 @@ +import DS from 'ember-data' + +export default DS.Model.extend + type: DS.attr('string', defaultValue: 'warn') + + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_constraints') + constraint_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition') + + restrict: Ember.computed.equal 'type', 'restrict' diff --git a/app/models/panda/workflow-action-definition.coffee b/app/models/panda/workflow-action-definition.coffee new file mode 100644 index 0000000..98ddc75 --- /dev/null +++ b/app/models/panda/workflow-action-definition.coffee @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/workflow-action-definition' diff --git a/app/models/panda/workflow-action-offset.coffee b/app/models/panda/workflow-action-offset.coffee new file mode 100644 index 0000000..69d7c29 --- /dev/null +++ b/app/models/panda/workflow-action-offset.coffee @@ -0,0 +1,10 @@ +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +Model = + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_offsets') + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition') + project_scenario: DS.belongsTo('panda/project-scenario') +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/app/models/panda/workflow-group-constraint.coffee b/app/models/panda/workflow-group-constraint.coffee new file mode 100644 index 0000000..f4dc37d --- /dev/null +++ b/app/models/panda/workflow-group-constraint.coffee @@ -0,0 +1,7 @@ +import DS from 'ember-data' +export default DS.Model.extend + type: DS.attr('string') + + workflow_group: DS.belongsTo('panda/workflow-group', inverse: 'constraints') + constraint_workflow_group: DS.belongsTo('panda/workflow-group') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', serialize: 'ids') diff --git a/app/models/panda/workflow-group.coffee b/app/models/panda/workflow-group.coffee new file mode 100644 index 0000000..70c7a56 --- /dev/null +++ b/app/models/panda/workflow-group.coffee @@ -0,0 +1,24 @@ +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +Model = + identifier: DS.attr('string') + name: DS.attr('string') + position: DS.attr('number', defaultValue: 0) + offset_type: DS.attr('string', defaultValue: 'none') # none, connected_finish, connected_start + + project: DS.belongsTo('panda/project') + constraints: DS.hasMany('panda/workflow-group-constraint', inverse: 'workflow_group') + tracker_groups: DS.hasMany('panda/tracker-group', serialize: 'ids') + workflow_action_definitions: DS.hasMany('panda/workflow_action_definition') + project_scenarios: DS.hasMany('panda/project-scenario') + + offset_base: DS.belongsTo('panda/workflow-group', inverse: 'offset_dependents') + offset_dependents: DS.hasMany('panda/workflow-group', inverse: null) # do not give with api, will clear inverse if not given + + workflowActionDefinitionSort: ['position:asc'] + sorted_workflow_action_definitions: Ember.computed.sort('workflow_action_definitions', 'workflowActionDefinitionSort') +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'duration', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/app/models/panda/workflow-instance-type.coffee b/app/models/panda/workflow-instance-type.coffee new file mode 100644 index 0000000..29da88f --- /dev/null +++ b/app/models/panda/workflow-instance-type.coffee @@ -0,0 +1,6 @@ +import DS from 'ember-data' + +export default DS.Model.extend + name: DS.attr('string') + #organization: DS.belongsTo('organization') + projects: DS.hasMany('panda/project', inverse: 'workflow_instance_type') diff --git a/app/routes/action-list.js b/app/routes/action-list.js new file mode 100644 index 0000000..6831a2a --- /dev/null +++ b/app/routes/action-list.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/routes/action-list'; diff --git a/app/routes/organization.coffee b/app/routes/organization.coffee new file mode 100644 index 0000000..2e7d03c --- /dev/null +++ b/app/routes/organization.coffee @@ -0,0 +1,33 @@ +import Ember from 'ember' +{get} = Ember +export default Ember.Route.extend + session: Ember.inject.service('session') + actions: + editOrganization: (organization)-> + @modal 'organization/edit-organization', + model: organization.get('changeset') + + newOrganization: -> + organization = @store.createRecord('panda/organization') + @modal 'organization/edit-organization', + model: organization.get('changeset') + title: t('action.new_model', model: t('models.organization')) + + setLogo: (organization, file) -> + return unless file + base = @ + session = @get('session') + #TODO implement generic request feature, this is not the way to do it! Done because it works and no time + if get(session, 'isAuthenticated') + session.authorize 'authorizer:devise', (key, authorization) -> + file.upload organization.action_url('logo'), + headers: + "#{key}": authorization + #authorization: "Token token=\"#{@get('session.session.content.authenticated.token')}\", email=\"#{@get('session.session.content.authenticated.email')}\"" + .then (response) -> + organization.store.pushPayload response.body if response?.body?.data + else # assume same host authenticated rails session + file.upload(organization.action_url('logo')).then (response) -> + organization.store.pushPayload response.body if response?.body?.data + + @send 'closeModal' diff --git a/app/routes/organization/index.coffee b/app/routes/organization/index.coffee new file mode 100644 index 0000000..ef924fa --- /dev/null +++ b/app/routes/organization/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('panda/organization') diff --git a/app/routes/organization/show.coffee b/app/routes/organization/show.coffee new file mode 100644 index 0000000..d7e3545 --- /dev/null +++ b/app/routes/organization/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options)-> @store.findRecord('panda/organization', options.organization_id, reload: false) diff --git a/app/routes/project.coffee b/app/routes/project.coffee new file mode 100644 index 0000000..66975f5 --- /dev/null +++ b/app/routes/project.coffee @@ -0,0 +1,25 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + editProject: (project)-> + @modal 'project/edit-project', + model: project + + newProject: -> + project = @store.createRecord 'panda/project' + @modal 'project/edit-project', + model: project + title: t('action.new_model', model: t('models.project')) + + projectContract: (project) -> + @modal 'project/contract', + model: project + + new_project_from_contract: (project) -> + #project ||= @store.createRecord('panda/project') + project.rollbackAttributes() if project?.get('isNew') + @modal 'project/new-project-from-contract' + + show_project_scenarios_grid: (project) -> + @modal 'project/project-scenarios-show-grid', + model: project diff --git a/app/routes/project/index.coffee b/app/routes/project/index.coffee new file mode 100644 index 0000000..e315d98 --- /dev/null +++ b/app/routes/project/index.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.findAll('panda/project') + actions: + editProjectsContext: -> + @modal 'project/edit-projects-context' diff --git a/app/routes/project/show.coffee b/app/routes/project/show.coffee new file mode 100644 index 0000000..9cbb2c4 --- /dev/null +++ b/app/routes/project/show.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (params) -> @store.findRecord('panda/project', params.project_id) + #model: (params) -> + # @store.findRecord('panda/project', params.project_id, backgroundReload: false).then (project) -> + # unless project.get('project_show_reloaded') + # console.log "Trigger project show reloading for #{project.get('id')}" + # project.reload().then project.set('project_show_reloaded', true) + # project diff --git a/app/routes/project/show/add-progress.coffee b/app/routes/project/show/add-progress.coffee new file mode 100644 index 0000000..6c3360f --- /dev/null +++ b/app/routes/project/show/add-progress.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @modelFor('project.show') diff --git a/app/routes/project/show/dunlop.coffee b/app/routes/project/show/dunlop.coffee new file mode 100644 index 0000000..046810a --- /dev/null +++ b/app/routes/project/show/dunlop.coffee @@ -0,0 +1,16 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @modelFor('project.show') + + afterModel: (model, transition) -> + model.reload() unless @controllerFor('application').get('currentRouteName') in ['loading', 'project.show.dunlop'] + + actions: + edit_selected_project_scenario: -> + #return unless project_scenario = @get('controller.selected_project_scenarios.firstObject') + return unless project_scenario = @get('controller.active_project_scenario') + @modal 'project/edit-project-scenario', + model: project_scenario + closeOnOverlay: false + destroy_callback: => + @set 'controller.selected_project_scenario', null diff --git a/app/routes/project/show/project-instance-action/index.coffee b/app/routes/project/show/project-instance-action/index.coffee new file mode 100644 index 0000000..eb138c1 --- /dev/null +++ b/app/routes/project/show/project-instance-action/index.coffee @@ -0,0 +1,33 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + workflow_action_definition_id_in: [] + project_instance_group_workflow_group_id: '' + state_in: [] + target_date_gteq: null + target_date_lteq: null + s: 'target_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + controller.set 'project', @modelFor('project.show') + @_super arguments... + + model: (params) -> + project = @modelFor 'project.show' + #@store.findAll('epots/migration-window-planning') + default_filter.project_id = project.id + otherParams = {q: default_filter.toProperties()} + PagedRemoteArray.create + modelName: 'panda/project-instance-action' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' diff --git a/app/routes/project/show/project-instance-action/show.coffee b/app/routes/project/show/project-instance-action/show.coffee new file mode 100644 index 0000000..249a28c --- /dev/null +++ b/app/routes/project/show/project-instance-action/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> @store.peekRecord('panda/project-instance-action', options.project_instance_action_id) diff --git a/app/routes/project/show/project-instance-group/index.coffee b/app/routes/project/show/project-instance-group/index.coffee new file mode 100644 index 0000000..3fc6547 --- /dev/null +++ b/app/routes/project/show/project-instance-group/index.coffee @@ -0,0 +1,8 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (params) -> + #project = @modelFor('project.show') + #Ember.RSVP.hash + # project: project + # project_instance_groups: @store.peekAll('panda/project-instance-group').filterBy('project_instance.project.id', project.get('id')) + @modelFor 'project.show' diff --git a/app/routes/project/show/project-instance-group/show.coffee b/app/routes/project/show/project-instance-group/show.coffee new file mode 100644 index 0000000..545ab9d --- /dev/null +++ b/app/routes/project/show/project-instance-group/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> @store.peekRecord('panda/project-instance-group', options.project_instance_group_id) diff --git a/app/routes/project/show/project-instance/index.coffee b/app/routes/project/show/project-instance/index.coffee new file mode 100644 index 0000000..0da9f33 --- /dev/null +++ b/app/routes/project/show/project-instance/index.coffee @@ -0,0 +1,39 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + s: 'reference_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + controller.set 'project', @modelFor('project.show') + @_super arguments... + + model: (params) -> + project = @modelFor 'project.show' + #@store.findAll('epots/migration-window-planning') + default_filter.project_id = project.id + otherParams = {q: default_filter.toProperties()} + PagedRemoteArray.create + modelName: 'panda/project-instance' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' + + actions: + new_project_instance: -> + route = @ + @modal 'project/show/project-instance/new-project-instance', + model: @store.createRecord('panda/project-instance', project: @modelFor('project.show')) + title: t('action.new_model', model: t('models.project_instance')) + save: (record) -> + route.transitionTo('project.show.project-instance.show', record.id) if record.id + + diff --git a/app/routes/project/show/project-instance/show.coffee b/app/routes/project/show/project-instance/show.coffee new file mode 100644 index 0000000..d3845e6 --- /dev/null +++ b/app/routes/project/show/project-instance/show.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> + @store.findRecord('panda/project-instance', options.project_instance_id) + afterModel: (model, transition) -> + model.reload() if @controllerFor('application').get('currentRouteName') in ['action-list', 'project.show.project-instance.index'] + setupController: (controller, model) -> + controller.set 'project', @modelFor('project.show') + @_super arguments... diff --git a/app/routes/project/show/tracker-subject.coffee b/app/routes/project/show/tracker-subject.coffee new file mode 100644 index 0000000..3043eb6 --- /dev/null +++ b/app/routes/project/show/tracker-subject.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> + Ember.RSVP.hash + project: @modelFor('project.show') + tracker_subject: @store.findRecord('panda/tracker-subject', options.tracker_subject_id) diff --git a/app/routes/tracker-subject/index.coffee b/app/routes/tracker-subject/index.coffee new file mode 100644 index 0000000..503814a --- /dev/null +++ b/app/routes/tracker-subject/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('panda/tracker-subject') diff --git a/app/routes/tracker-subject/show/project.coffee b/app/routes/tracker-subject/show/project.coffee new file mode 100644 index 0000000..3a35eba --- /dev/null +++ b/app/routes/tracker-subject/show/project.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options)-> + Ember.RSVP.hash + project: @store.findRecord('panda/project', options.project_id) + tracker_subject: @modelFor('tracker-subject.show') diff --git a/app/routes/tracker_subject.coffee b/app/routes/tracker_subject.coffee new file mode 100644 index 0000000..8c95ba1 --- /dev/null +++ b/app/routes/tracker_subject.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + newTrackerSubject: -> + tracker_subject = @store.createRecord 'panda/tracker-subject' + @modal "edit-tracker-subject", + model: tracker_subject + title: t('action.new_model', model: t('models.tracker_subject')) + + editTrackerSubject: (tracker_subject)-> + @modal 'edit-tracker-subject', + model: tracker_subject diff --git a/app/routes/user.coffee b/app/routes/user.coffee new file mode 100644 index 0000000..8522fca --- /dev/null +++ b/app/routes/user.coffee @@ -0,0 +1,11 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + editUser: (user)-> + @modal 'user/edit-user', + model: user + newUser: -> + new_user = @store.createRecord('user') + @modal 'user/new-user', + model: new_user + save: => @transitionTo 'user.show', new_user diff --git a/app/routes/user/index.coffee b/app/routes/user/index.coffee new file mode 100644 index 0000000..ab24a7b --- /dev/null +++ b/app/routes/user/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('user') # fetched in application route diff --git a/app/routes/user/profile.coffee b/app/routes/user/profile.coffee new file mode 100644 index 0000000..fd34537 --- /dev/null +++ b/app/routes/user/profile.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @get('router.globals.current_user') diff --git a/app/routes/user/show.coffee b/app/routes/user/show.coffee new file mode 100644 index 0000000..91c7c4e --- /dev/null +++ b/app/routes/user/show.coffee @@ -0,0 +1,34 @@ +import Ember from 'ember' +export panda_authorizations = [ + {type: 'model', target: 'panda/organization', permissions: ['manage']} + {type: 'model', target: 'panda/project', permissions: ['read', 'manage']} + {type: 'model', target: 'panda/project-instance', permissions: ['read', 'manage']} +] +export default Ember.Route.extend + setupController: (controller, model) -> + adapters = @store.peekAll('panda/adapter') + active_authorizations = model.get('role_names') + + active_authorizations.push "ban-app-panda" unless active_authorizations.some (permission) -> + permission.match new RegExp('-app-panda$') + panda_authorizations.forEach (authorization) -> + match_found = false + authorization.permissions.forEach (permission) -> + match_found = true if "#{permission}-#{authorization.type}-#{authorization.target}" in active_authorizations + active_authorizations.push "ban-#{authorization.type}-#{authorization.target}" unless match_found + + # explicitly add ban-model-epots/work-order if no permission is present + adapters.forEach (adapter) -> + adapter.get('authorizations').forEach (authorization) -> + match_found = false + authorization.permissions.forEach (permission) -> + match_found = true if "#{permission}-#{authorization.type}-#{authorization.target}" in active_authorizations + active_authorizations.push "ban-#{authorization.type}-#{authorization.target}" unless match_found + has_adapter_authorization = active_authorizations.some (permission) -> + permission.match(new RegExp("-adapter-#{adapter.id}$")) + active_authorizations.push "ban-adapter-#{adapter.id}" unless has_adapter_authorization + + controller.set 'active_authorizations', active_authorizations + controller.set 'adapters', adapters + controller.set 'panda_authorizations', panda_authorizations + @_super arguments... diff --git a/app/templates/action-list.emblem b/app/templates/action-list.emblem new file mode 100644 index 0000000..a11dbf2 --- /dev/null +++ b/app/templates/action-list.emblem @@ -0,0 +1,124 @@ +.ui.top-right.pagination.menu= page-numbers content=model.project_instance_actions showFL=true += page-title 'action_list.title' + = pagination-progress content=model.project_instance_actions class='between-brackets' +.ui.top.attached.segment + = ui-dropdown class='violet button pull-right' + .default.text Export + i.dropdown.icon + .menu + .item + a href=export_human_url: span Human + .item + a href=export_technical_url: span Technical + = action-list/project-filter apply_filters=(action 'refresh_list') filters=filters scope_project=scope_project scope_scenario=scope_scenario projects=model.projects + = action-list/organization-filter apply_filters=(action 'refresh_list') filters=filters scope_organization=scope_organization organizations=model.organizations + ' + .ui.small.icon.button{action 'refresh_list'} + i.refresh.icon +table.ui.attached.stiped.table + thead + tr + unless scope_project + th= t 'models.project' + unless scope_scenario + th= t 'models.project_scenario' + unless scope_organization + th= t 'models.organization' + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'models.project_instance' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.notes' + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + table-filters content=model.project_instance_actions filters=filters as |filter| + unless scope_project + th + unless scope_scenario + th + unless scope_organization + th + if (and scope_project scope_project.isLoaded) + = filter.dropdown-select key='project_instance_group_workflow_group_id' options=scope_project.sorted_workflow_groups labelPath='name' + = filter.dropdown-select-multiple key='workflow_action_definition_id_in' options=scope_project.sorted_workflow_action_definitions labelPath='title' + else + th + = filter.input key='workflow_action_definition_title_cont' + = filter.input key='project_instance_client_identifier_cont' + = filter.dropdown-select key='state' options=globals.project_instance_action.states translationPath='attributes.state' + = filter.input key='notes_cont' + = filter.date-range key='target_date' expandingTime=true weekSelector=true + th + tbody + if model.project_instance_actions.loading + tr + td colspan='99' + .ui.active.inline.centered.loader + else + each model.project_instance_actions as |project_instance_action| + tr.record-with-pointing-records + unless scope_project + td= project_instance_action.project.name + unless scope_scenario + td= project_instance_action.project_instance.project_scenario.name + unless scope_organization + td= project_instance_action.workflow_action_definition.organization.name + td + if project_instance_action.project_instance_group.id + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + = project_instance_action.project_instance_group.workflow_group.name + else + = project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + = project_instance_action.project_instance.client_identifier + else + = project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action.state action=(action 'edit_project_instance_action' project_instance_action) + td + if (gt project_instance_action.notes.length 14) + = ui-markdown-popup tagName='i' class='sticky note outline icon' markdown=project_instance_action.notes + else if project_instance_action.notes + = project_instance_action.notes + if project_instance_action.workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info circle icon' markdown=project_instance_action.workflow_action_definition.extra_info + td= format-date project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action + each project_instance_action.pointing_project_instance_action_connections as |project_instance_action_connection| + if (eq project_instance_action_connection.connection_type 'attached') + tr.pointing-record + unless scope_project + td= project_instance_action_connection.project_instance_action.project.name + unless scope_scenario + td= project_instance_action_connection.project_instance_action.project_instance.project_scenario.name + unless scope_organization + td= project_instance_action_connection.project_instance_action.workflow_action_definition.organization.name + td + if project_instance_action_connection.project_instance_action.project_instance_group.id + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + = project_instance_action.project_instance_group.workflow_group.name + else + = project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action_connection.project_instance_action.workflow_action_definition.title + td + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + = project_instance_action.project_instance.client_identifier + else + = project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action_connection.project_instance_action.state action=(action 'edit_project_instance_action' project_instance_action_connection.project_instance_action) + td + if (gt project_instance_action_connection.project_instance_action.notes.length 14) + = ui-markdown-popup tagName='i' class='sticky note outline icon' markdown=project_instance_action_connection.project_instance_action.notes + else if project_instance_action_connection.project_instance_action.notes + = project_instance_action_connection.project_instance_action.notes + if project_instance_action_connection.project_instance_action.workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info circle icon' markdown=project_instance_action_connection.project_instance_action.workflow_action_definition.extra_info + td= format-date project_instance_action_connection.project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action_connection.project_instance_action diff --git a/app/templates/components/action-list/organization-filter.emblem b/app/templates/components/action-list/organization-filter.emblem new file mode 100644 index 0000000..f3ee00e --- /dev/null +++ b/app/templates/components/action-list/organization-filter.emblem @@ -0,0 +1,12 @@ +.ui.small.buttons.select-organization + = ui-dropdown class='button' selected=scope_organization onChange=(action 'set_scope_organization') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.organization' + i.dropdown.icon + .menu + each organizations as |organization| + .item data-value='{{map-value mapper organization}}' + = organization.name + if scope_organization + button.ui.icon.button{action 'set_scope_organization' null} + i.close.icon += yield diff --git a/app/templates/components/action-list/project-filter.emblem b/app/templates/components/action-list/project-filter.emblem new file mode 100644 index 0000000..c482a6b --- /dev/null +++ b/app/templates/components/action-list/project-filter.emblem @@ -0,0 +1,26 @@ +.ui.small.buttons.select-project + = ui-dropdown class='button' selected=scope_project onChange=(action 'set_scope_project') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project' + i.dropdown.icon + .menu + each (sort-by 'name' projects) as |project| + .item data-value='{{map-value mapper project}}' + = project.name + if scope_project + button.ui.icon.button{action 'set_scope_project' null} + i.close.icon +' +if scope_project + .ui.small.buttons.select-project-scenario + = ui-dropdown class='button' selected=scope_scenario onChange=(action 'set_scope_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + each scope_project.project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + if scope_scenario + button.ui.icon.button{action 'set_scope_scenario' null} + i.close.icon + ' += yield diff --git a/app/templates/components/edit-attribute-item.emblem b/app/templates/components/edit-attribute-item.emblem new file mode 100644 index 0000000..144e4b0 --- /dev/null +++ b/app/templates/components/edit-attribute-item.emblem @@ -0,0 +1,22 @@ +/.ui.form + .ui.right.floated.icon.buttons + if isEditing + = push-action type='rollback' action='rollbackRecord' + = push-action type='destroy' action='destroyRecord' + = push-action type='save' action='saveRecord' + else + if can_make_editable + /= push-action type='edit' action='editRecord' + if isEditing + = input value=model.name enter='saveRecord' + else + span= model.name +if isEditing + .ui.action.input + = input value=model.name enter='saveRecord' + = push-action type='rollback' action='rollbackRecord' + = push-action type='destroy' action='destroyRecord' + = push-action type='save' action='saveRecord' +else + span= model.name + = yield diff --git a/app/templates/components/flash-message.emblem b/app/templates/components/flash-message.emblem new file mode 100644 index 0000000..754ec50 --- /dev/null +++ b/app/templates/components/flash-message.emblem @@ -0,0 +1 @@ += message diff --git a/app/templates/components/format-json.emblem b/app/templates/components/format-json.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/format-json.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/labeled-content-choice.emblem b/app/templates/components/labeled-content-choice.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/labeled-content-choice.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/number-input-with-value.emblem b/app/templates/components/number-input-with-value.emblem new file mode 100644 index 0000000..02832f6 --- /dev/null +++ b/app/templates/components/number-input-with-value.emblem @@ -0,0 +1,2 @@ +one-way-input stringValue type='number' step=step update='set_value' += yield diff --git a/app/templates/components/project-workflow-groups-graph.emblem b/app/templates/components/project-workflow-groups-graph.emblem new file mode 100644 index 0000000..40a8112 --- /dev/null +++ b/app/templates/components/project-workflow-groups-graph.emblem @@ -0,0 +1,2 @@ +pre.hide= digraph_content += svg_content diff --git a/app/templates/components/project/action-execution-offset-diagram.emblem b/app/templates/components/project/action-execution-offset-diagram.emblem new file mode 100644 index 0000000..1a820e0 --- /dev/null +++ b/app/templates/components/project/action-execution-offset-diagram.emblem @@ -0,0 +1,26 @@ +.ui.grid + .nine.wide.column= graph_title + .right.aligned.seven.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +.action-execution-offset-diagram-container.diagram-container +.range-slider-container + if range_start_default + range-slider start=range_start_default range=range_bounds on-change='range_changed' tooltips=range_formatter step=1 connect=true +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/project/edit-active-project-scenario.emblem b/app/templates/components/project/edit-active-project-scenario.emblem new file mode 100644 index 0000000..26b205e --- /dev/null +++ b/app/templates/components/project/edit-active-project-scenario.emblem @@ -0,0 +1,15 @@ += ui-dropdown class='button' selected=active_project_scenario onChange=(action (mut active_project_scenario)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .header + i.grid.layout.icon + a.pointable{action 'show_project_scenarios_grid' project} Show activation grid + each (sort-by 'name' project.project_scenarios) as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name +if active_project_scenario + button.ui.icon.button{action (mut active_project_scenario) null} + i.close.icon + button.ui.edit.icon.button{action 'edit_selected_project_scenario'} + i.edit.icon diff --git a/app/templates/components/project/instance-action-value-display.emblem b/app/templates/components/project/instance-action-value-display.emblem new file mode 100644 index 0000000..73f8811 --- /dev/null +++ b/app/templates/components/project/instance-action-value-display.emblem @@ -0,0 +1,42 @@ +if (eq value_type 'boolean') + if project_instance_action.boolean_value + i.checkmark.box.icon + else + i.square.outline.value-missing.icon +else if (eq value_type 'date') + if project_instance_action.date_value + = format-date project_instance_action.date_value format=globals.date_formatting + else + i.calendar.value-missing.icon +else if (eq value_type 'timestamp') + if project_instance_action.timestamp_value + = format-date project_instance_action.timestamp_value format=globals.time_formatting + else + i.wait.value-missing.icon +else if (eq value_type 'number') + if project_instance_action.number_value + = project_instance_action.number_value + else + i.hashtag.value-missing.icon +else if (eq value_type 'select') + if project_instance_action.string_value + = project_instance_action.string_value + else + i.list.value-missing.icon +else if (eq value_type 'string') + if project_instance_action.string_value + = project_instance_action.string_value + else + i.terminal.value-missing.icon +else if (eq value_type 'text') + if project_instance_action.text_value + if (gt project_instance_action.text_value.length 14) + = ui-markdown-popup tagName='i' class='sticky note icon' markdown=project_instance_action.text_value + else + = project_instance_action.text_value + else + i.sticky.note.outline.value-missing.icon +else + span= value_type + i.red.warning.circle.icon + = project_instance_action.value_display diff --git a/app/templates/components/project/scenario-edit-item.emblem b/app/templates/components/project/scenario-edit-item.emblem new file mode 100644 index 0000000..7e58e57 --- /dev/null +++ b/app/templates/components/project/scenario-edit-item.emblem @@ -0,0 +1,3 @@ +.ui.right.floated.icon.buttons + = push-action type='edit' action='editProjectScenario' +p= model.name diff --git a/app/templates/components/project/workflow-action-constraints-graph.emblem b/app/templates/components/project/workflow-action-constraints-graph.emblem new file mode 100644 index 0000000..5b8d75d --- /dev/null +++ b/app/templates/components/project/workflow-action-constraints-graph.emblem @@ -0,0 +1,5 @@ +/if has_content + .ui.header: .content= t 'models.plural.workflow_action_constraint' + /pre.hide2= digraph_content + /h1= workflow_action_constraints.length += svg_content diff --git a/app/templates/components/project/workflow-action-offsets-graph.emblem b/app/templates/components/project/workflow-action-offsets-graph.emblem new file mode 100644 index 0000000..13f5cb9 --- /dev/null +++ b/app/templates/components/project/workflow-action-offsets-graph.emblem @@ -0,0 +1,34 @@ +/if has_content + .ui.header: .content= t 'models.plural.workflow_action_offset' + pre= digraph_contents.firstObject +.ui.grid + .eleven.wide.column + = graph_title + = ui-checkbox checked=show_attached label=(t 'workflow_action_definition.offset_graph.show_attached') onChange=(action (mut show_attached)) + ' + = ui-checkbox checked=show_pointing_infos label=(t 'workflow_action_definition.offset_graph.show_pointing_infos') onChange=(action (mut show_pointing_infos)) + ' + = ui-checkbox checked=show_pointing_constraints label=(t 'workflow_action_definition.offset_graph.show_pointing_constraints') onChange=(action (mut show_pointing_constraints)) + if project.project_scenarios.length + = project/edit-active-project-scenario project=project active_project_scenario=active_project_scenario + .right.aligned.five.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +each svg_contents as |svg_content| + == svg_content +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/project/workflow-group-offset-diagram.emblem b/app/templates/components/project/workflow-group-offset-diagram.emblem new file mode 100644 index 0000000..fb4f30d --- /dev/null +++ b/app/templates/components/project/workflow-group-offset-diagram.emblem @@ -0,0 +1,27 @@ +.ui.grid.without-bottom-margin + .nine.wide.column= graph_title + .right.aligned.seven.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +.workflow-group-offset-diagram-container.diagram-container +.range-slider-container + if range_start_default + range-slider start=range_start_default range=range_bounds on-change='range_changed' tooltips=range_formatter step=1 connect=true + +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/select-duration.emblem b/app/templates/components/select-duration.emblem new file mode 100644 index 0000000..c93970a --- /dev/null +++ b/app/templates/components/select-duration.emblem @@ -0,0 +1,27 @@ +if threshold + .ui.left.labeled.input + .ui.label Max + = number-input numericValue=value step=1 +else + .ui.input= number-input numericValue=value step=1 += ui-dropdown class='selection' selected=unit onChange=(action (mut unit)) + /.default.text= t model.batch_element_threshold_unit scope='date.unit.plural' bare=true + .text + i.dropdown.icon + .menu + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + .item data-value=batch_element_threshold_unit + if threshold + span per + = t batch_element_threshold_unit scope='date.unit' + else + = t batch_element_threshold_unit scope=value_date_scope +if unit_businessday + span class=has_default_business_days:default-business-days:non-default-business-days + = ui-dropdown class='right basic labeled icon select-businessdays-in-week' action='nothing' + i.dropdown.icon + span.ui.tiny.header= t 'date.unit.plural.day' + .menu + .scrolling.menu + each globals.week_days as |week_day| + ui-checkbox class='item' label=(t week_day.name scope='date.week_day') checked=(contains week_day.index week_days) onChange=(action 'toggle_week_day_presence' week_days week_day) diff --git a/app/templates/components/select-week-days.emblem b/app/templates/components/select-week-days.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/select-week-days.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/set-workflow-group-tracker-group.emblem b/app/templates/components/set-workflow-group-tracker-group.emblem new file mode 100644 index 0000000..54ca1e0 --- /dev/null +++ b/app/templates/components/set-workflow-group-tracker-group.emblem @@ -0,0 +1,9 @@ +if isEditing + span= workflow_group.name + x-select value=selectedViewItem action="selectViewItem" as |xs| + option -- + each tracker_subject.sorted_tracker_groups as |tracker_group| + xs.option value=tracker_group + = tracker_group.name +else + span= workflow_group.name diff --git a/app/templates/components/state-badge.emblem b/app/templates/components/state-badge.emblem new file mode 100644 index 0000000..80e6b50 --- /dev/null +++ b/app/templates/components/state-badge.emblem @@ -0,0 +1,2 @@ += human_state += yield diff --git a/app/templates/components/ui-multi-select.emblem b/app/templates/components/ui-multi-select.emblem new file mode 100644 index 0000000..04f77f9 --- /dev/null +++ b/app/templates/components/ui-multi-select.emblem @@ -0,0 +1,6 @@ +ui-dropdown class="fluid multiple selection" selected=selected onChange=(action (mut selected)) as |execute mapper| + i.dropdown.icon + .default.text= placeholder_text + .menu + each options as |option| + .item data-value="{{map-value mapper option}}"= get option labelProperty diff --git a/app/templates/components/value-type-display.emblem b/app/templates/components/value-type-display.emblem new file mode 100644 index 0000000..4f1c26f --- /dev/null +++ b/app/templates/components/value-type-display.emblem @@ -0,0 +1 @@ +i.icon class=iconClass diff --git a/app/templates/components/workflow-action-definition-item.emblem b/app/templates/components/workflow-action-definition-item.emblem new file mode 100644 index 0000000..a041976 --- /dev/null +++ b/app/templates/components/workflow-action-definition-item.emblem @@ -0,0 +1,31 @@ +if isEditing + .ui.one.column.grid + .ui.form + .column + .ui.tiny.right.floated.icon.buttons + = push-action type='rollback' action='rollbackWorkflowActionDefinition' + = push-action type='destroy' action='destroyWorkflowActionDefinition' + = push-action type='position' action='positionInProject' + = push-action type='edit' action='editWorkflowActionDefinition' + unless workflow_action_definition.isNew + = push-action type='authorization' action='authorize_users' + = push-action type='save' action='saveWorkflowActionDefinition' + = input value=workflow_action_definition.title enter='saveWorkflowActionDefinition' class='workflow-action-definition-title-edit' placeholder='name' + .column + = textarea value=workflow_action_definition.extra_info class='workflow-action-definition-extra-info-edit' placeholder='extra info' +else + .ui.grid.workflow-action-definition-row + .sixteen.wide.column + value-type-display type=workflow_action_definition.value_type + .ui.tiny.right.floated.icon.buttons + if workflow_action_definition.organization.logo + button.ui.unpadded.button{action 'editWorkflowActionDefinition'} + img src=workflow_action_definition.organization.logo_url.small + /workflow_action_definition.position + else + button.ui.button{action 'editWorkflowActionDefinition'}= workflow_action_definition.position + span.title= workflow_action_definition.title + if workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info icon' markdown=workflow_action_definition.extra_info + diff --git a/app/templates/components/workflow-action-definition/modal-header.emblem b/app/templates/components/workflow-action-definition/modal-header.emblem new file mode 100644 index 0000000..cee9aec --- /dev/null +++ b/app/templates/components/workflow-action-definition/modal-header.emblem @@ -0,0 +1,11 @@ +.header + if model.workflow_group.id + span= model.workflow_group.name + else + span (unassigned) + i.right.arrow.icon + if model.isNew + span (new) + else + span= model.title += yield diff --git a/app/templates/components/workflow-group-edit-constraint.emblem b/app/templates/components/workflow-group-edit-constraint.emblem new file mode 100644 index 0000000..5aee47a --- /dev/null +++ b/app/templates/components/workflow-group-edit-constraint.emblem @@ -0,0 +1,17 @@ +.ui.grid + .four.wide.column + /x-select model=constraint_types value=constraint valueKey="value.type" required=true canSearch=false onSelect=(action 'selectConstraintType') + x-select value=constraint.type on-change=(action (mut constraint.type)) as |xs| + each constraint_types as |constraint_type| + xs.option value=constraint_type + = constraint_type + /select + each constraint_types as |constraint_type| + option value=constraint_type selected={eq constraint_type constraint.type} + .eight.wide.column + x-select value=constraint.constraint_workflow_group.content on-change=(action (mut constraint.constraint_workflow_group)) as |xs| + each constraint_workflow_groups as |constraint_workflow_group| + xs.option value=constraint_workflow_group + = constraint_workflow_group.name + ui-multi-select options=constraint.constraint_workflow_group.sorted_workflow_action_definitions labelProperty='name' selected=constraint.workflow_action_definitions + .two.wide.column= push-action type="destroy" action="destroyConstraint" diff --git a/app/templates/components/workflow-group-header.emblem b/app/templates/components/workflow-group-header.emblem new file mode 100644 index 0000000..a93f77d --- /dev/null +++ b/app/templates/components/workflow-group-header.emblem @@ -0,0 +1,13 @@ +if workflow_group.isEditing + .ui.small.right.floated.icon.buttons + = push-action type='rollback' action='rollbackWorkflowStep' + = push-action type='save' action='saveWorkflowStep' + = push-action type='destroy' action='destroyWorkflowStep' + = input value=workflow_group_name enter='saveWorkflowStep' class='workflow-group-title-edit' +else + .ui.small.right.floated.icon.buttons + = push-action type='new' action='newWorkflowActionDefinition' + = push-action type='edit' action='editWorkflowGroup' + = push-action type='sort' action='sortWorkflowActionDefinitions' + = push-action type='show' action='displayWorkflowActionDefinitions' + = workflow_group.name diff --git a/app/templates/event.emblem b/app/templates/event.emblem new file mode 100644 index 0000000..f2ee372 --- /dev/null +++ b/app/templates/event.emblem @@ -0,0 +1,74 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + span= t 'models.plural.event' +.ui.container + .ui.header + /.ui.right.floated.icon.buttons + .content= t 'models.plural.event' + .ui.divider + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Events files (proposal) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Type, + td Instance Identifier, + td Event Identifier, + td Event key, + td Event value, + td Event timestamp + td Source + tbody + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td value, + td NEW12345, + td 2017-06-05T13:11:50Z, + td + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td state, + td completed, + td 2017-06-05T13:11:50Z, + td + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td date, + td 2017-09-28, + td 2017-06-05T13:11:50Z, + td + tr + td group, + td CFK42665, + td (group)5d4d6a60-46b0-11e7-93e6-02429821d4da, + td state, + td overdue, + td 2017-06-05T13:11:50Z, + td + tr + td instance, + td CFK42665, + td (project)9898343a-45eb-11e7-ae9b-02429821d4da, + td state, + td overdue, + td 2017-06-05T13:11:50Z, + td + tr + td instance-update, + td CFK42665, + td (project)9898343a-45eb-11e7-ae9b-02429821d4da, + td new_xdf_service_id, + td CFK42666, + td 2017-06-05T13:11:50Z, + td diff --git a/app/templates/manual.emblem b/app/templates/manual.emblem new file mode 100644 index 0000000..1e992b4 --- /dev/null +++ b/app/templates/manual.emblem @@ -0,0 +1,16 @@ +.ui.grid.manual + .four.wide.column.nav-column + ui-sticky + .ui.vertical.menu + .header.item= link-to 'manual': span= t 'manual.title' + .item + .header= t 'models.plural.project' + .menu + = link-to 'manual.project.dunlop' class='item' + span= t 'project.dunlop.title' + .item + .header= t 'models.plural.tracker_subject' + .menu + = link-to 'manual.tracker.index' class='item' + span Basis + .twelve.wide.column= outlet diff --git a/app/templates/manual/index.emblem b/app/templates/manual/index.emblem new file mode 100644 index 0000000..f753a2a --- /dev/null +++ b/app/templates/manual/index.emblem @@ -0,0 +1,41 @@ +.ui.container + = page-title 'manual.title' + p= t 'manual.introduction1' + + h1.ui.header Conceptueel project design + .ui.horizontal.divider Definitie (nvt) + .ui.horizontal.divider Design (nvt) + .ui.horizontal.divider Initialisatie (nvt) + .ui.horizontal.divider Forecasting + .ui.horizontal.divider Resource distribution + p Een optimale doelplanning maken op basis van de forecast waarbij een geoptimaliseerde inzet van resources het doel is. + p + | Mogelijke voorbeelden zijn: + ul + li Het zo gelijkmatig werk verdelen over de week + li Zo veel mogelijk aan het begin van de week om meer tijd over te houden voor incidenten aan het einde van de week + li In afstemming met eventuele externe resource bronnen + + .ui.horizontal.divider Planning + p Het daadwerkelijk vastleggen van een beoogde planning (harde plan datum) + + .ui.horizontal.divider Aansturing + p Aansturen van betrokken organisaties + + .ui.horizontal.divider Progress consumption + p Het verwerken van de daadwerkelijke acties binnen het project. Ofwel via bronnen van externe organisaties, danwel direct via user input + + .ui.horizontal.divider Prognosis and velocity projections + p ... + + .ui.horizontal.divider Project insights + p Het kunnen inzien van de project voortgang en eventuele acties die te laat zijn afgemeld + + .ui.horizontal.divider Detection + p Systeem triggers op bekende mogelijke afwijkingen met actieve gebruikers handeling als gevolg (attention-incident) + + .ui.horizontal.divider Investigation + p Het kunnen bepalen waar onverwachte problemen ontstaan + .ui.horizontal.divider Analysis + .ui.horizontal.divider Reporting +img src="./assets/manual/manual-index.png" diff --git a/app/templates/manual/project/dunlop.emblem b/app/templates/manual/project/dunlop.emblem new file mode 100644 index 0000000..a4b903a --- /dev/null +++ b/app/templates/manual/project/dunlop.emblem @@ -0,0 +1,38 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'Manual' 'manual.index' + breadcrumb.item + = t 'models.project' + breadcrumb.item active=true + = t 'project.dunlop.title' += page-title 'project.dunlop.title' + +h2.ui.dividing.header= t 'models.plural.workflow_group' +p= t 'manual.project.dunlop.workflow_group.text' modelPath='models.workflow_group' modelsPath='models.plural.workflow_group' +h3.ui.block.header= t 'manual.project.dunlop.workflow_group.duration_value.header' + +h2.ui.dividing.header= t 'models.plural.workflow_action_definition' +h3.ui.block.header= t 'manual.project.dunlop.workflow_action_definition.action_confirmation.header' +p= t 'manual.project.dunlop.workflow_action_definition.action_confirmation.text' + +h2.ui.dividing.header= t 'models.plural.workflow_action_offset' +h3.ui.block.header Offset diagram +p + | The offset diagram is a (digraph) representation of the actions. The direction top to bottom indicates time. This diagram is meant to indicate relative order, so this indication is for + one action relative to the other in time. Different action trees can be at different times and at the same horizontal level. +p The following diagram shows a configuration where B is configured to be 5 days before A (-5) and C 2 days after B +img src="../../assets/manual/action-offsets/non-rooted.png" title="non-rooted" +p + | Since none of these actions has any connection with the intended reference date (X), this tree is non-rooted. This green color is an indication of that.
Now lets root action A + to be 1 day before the intended reference date (X). +img src="../../assets/manual/action-offsets/rooted-critical-path.png" title="rooted-critical-path" +p + | The rooted action is indicated in blue, and the actions connected to rooted elements according to the critical path algorithm are indicated by a softer color green then the non-rooted actions. + The critical path indication here means that the configuration of action B being 5 days before A (-5) is located on action B. In other words, on the action having the longest path distance + to the X-rooted action. So the fact that action C is soft green indicates that the configuration of it being 2 days after B is located on action C. +p Another assumption for an action to belong to the critical path solution is that all actions at the other end of the connections configured in that action are also critical path actions. +p + | Configuring action B to be 2 days before C (-2) instead of C being 2 days after B will break the critical path assumption. Action C will be not connected to any other action, so being a + non-rooted tree in itself and action B will have a connection to this non-rooted tree (consisting only of 1 action in this case). +img src="../../assets/manual/action-offsets/rooted-non-critical-path.png" title="rooted-critical-path" +p The C action in this case also is a non critical path connection since it's connection with B is defined on B, so not the farthest away from the X-rooted (or specifically planned) action. diff --git a/app/templates/manual/tracker/index.emblem b/app/templates/manual/tracker/index.emblem new file mode 100644 index 0000000..0d1695a --- /dev/null +++ b/app/templates/manual/tracker/index.emblem @@ -0,0 +1,11 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'Manual' 'manual.index' + breadcrumb.item + = t 'models.plural.tracker_subject' + breadcrumb.item active=true + span Basis += page-title 'models.plural.tracker_subject' +p + | + Trackers zijn het mechanisme om project onafhankelijk analyses te kunnen instellen en inzien over alle voortgansdata. diff --git a/app/templates/modals/-display-workflow-action-definitions.emblem b/app/templates/modals/-display-workflow-action-definitions.emblem new file mode 100644 index 0000000..8055298 --- /dev/null +++ b/app/templates/modals/-display-workflow-action-definitions.emblem @@ -0,0 +1,15 @@ +if displayNumbered + ol + each workflow_action_definitions as |workflow_action_definition| + li= workflow_action_definition.title +else + each workflow_groups as |workflow_group| + if showTitles + h3= workflow_group.name + ul + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + li class=workflow_action_definition.done:done:active: span= workflow_action_definition.title + +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{ action "close"}= t 'general.close' + /= input type="checkbox" checked=showAll diff --git a/app/templates/modals/-edit-tracker-group.emblem b/app/templates/modals/-edit-tracker-group.emblem new file mode 100644 index 0000000..7edbc91 --- /dev/null +++ b/app/templates/modals/-edit-tracker-group.emblem @@ -0,0 +1,7 @@ +.form-row + .form-label= t 'attributes.organization.name' + .form-field= input value=model.name enter="saveRecord" +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/modals/-edit-tracker-subject.emblem b/app/templates/modals/-edit-tracker-subject.emblem new file mode 100644 index 0000000..335f0ec --- /dev/null +++ b/app/templates/modals/-edit-tracker-subject.emblem @@ -0,0 +1,19 @@ +.form-row + .form-label= t 'attributes.tracker_subject.name' + .form-field= input value=model.name enter="saveRecord" +.form-row + .form-label= t 'attributes.tracker_subject.edit_from_workflow_group' + .form-field= input checked=model.edit_from_workflow_group type="checkbox" +.form-row + .form-label= t 'models.plural.project' + .form-field= ui-multi-select options=model.organization.projects labelProperty="name" selected=model_projects +unless model.isNew + h3 + push-action type="new" action="newViewItem" class="right" + = t 'models.plural.tracker_group' + each model.tracker_groups as |tracker_group| + editable-attribute model=tracker_group +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/organization/-edit-organization.emblem b/app/templates/organization/-edit-organization.emblem new file mode 100644 index 0000000..70b2586 --- /dev/null +++ b/app/templates/organization/-edit-organization.emblem @@ -0,0 +1,26 @@ +table.ui.definition.table + tr + td= t 'attributes.organization.name' + td= validated-input attribute='name' enter="saveRecord" changeset=model + unless model.isNew + tr + td= t 'attributes.organization.logo' + td + if model._content.logo + img src=model._content.logo_url.small + = file-upload name='logo' onfileadd=(route-action "setLogo" model._content) + a.ui.button id="upload-image" tabindex=0 + i.upload.icon + span Replace logo + else + = file-upload name='logo' onfileadd=(route-action "setLogo" model._content) + a.ui.button id="upload-image" tabindex=0 + i.upload.icon + span Add logo + tr + td= t 'models.plural.user' + td= ui-multi-select options=possible_users labelProperty='nickname' selected=selected_users placeholder='organization.select_users.placeholder' +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{ action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{ action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{ action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/organization/index.emblem b/app/templates/organization/index.emblem new file mode 100644 index 0000000..ba0ed53 --- /dev/null +++ b/app/templates/organization/index.emblem @@ -0,0 +1,22 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.organization' +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + /= push-action type='edit' action='editOrganizationContext' + = push-action type='new' action='newOrganization' description='Add organization' + = t 'models.plural.organization' + table.ui.unstackable.bottom.attached.table + /thead + tr + th= t 'attributes.organization.name' + th.actions + tbody + each (sort-by 'name' model) as |organization| + tr + td.compact.collapsing + if organization.logo + img src=organization.logo_url.small + td= link-to organization.name 'organization.show' organization + td.actions= push-action type='edit' action='editOrganization' model=organization diff --git a/app/templates/organization/show/index.emblem b/app/templates/organization/show/index.emblem new file mode 100644 index 0000000..aad7b7f --- /dev/null +++ b/app/templates/organization/show/index.emblem @@ -0,0 +1,26 @@ +/.row: .small-12.columns: ul.breadcrumbs + li.current: a= model.name +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'organization': span= t 'models.plural.organization' + breadcrumb.item active=true + = model.name + +.ui.container + .ui.header + .ui.right.floated.icon.buttons + if is_manager + = push-action type='edit' action='editOrganization' + h2 + span.resource-descriptor= t 'models.organization' + span.resource-name= model.name + .ui.segment + if model.logo + img src=model.logo_url.large + ul.ui.list + li= link-to 'organization.show.project-actions' model + span= t 'organization.project_actions.title' + +/= line-chart data=chart_data +/= gantt-chart organization=model + diff --git a/app/templates/organization/show/project-actions.emblem b/app/templates/organization/show/project-actions.emblem new file mode 100644 index 0000000..4993a36 --- /dev/null +++ b/app/templates/organization/show/project-actions.emblem @@ -0,0 +1,9 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'organization.index': span= t 'models.plural.organization' + breadcrumb.item + span= t 'models.organization' + span : + = link-to 'organization.show' model: span= model.name + breadcrumb.item active=true + span= t 'organization.project_actions.title' diff --git a/app/templates/project/-contract.emblem b/app/templates/project/-contract.emblem new file mode 100644 index 0000000..be1e306 --- /dev/null +++ b/app/templates/project/-contract.emblem @@ -0,0 +1,16 @@ +.ui.visible.message + | A project's contract is a technical description of the project meant for communication, project updates + and project cloning. + In addition to this some other important objectives will be backup and project history logging. +if contract.isFulfilled + = format-json value=contract.content top_title='Contract' +else + .ui.icon.message + i.notched.circle.loading.icon + .content + .header Loading contract... +.modal-actions.sticky + if contract.isFulfilled + a.ui.primary.button.pull-right href=download_href download=download_file_name + span Download + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' diff --git a/app/templates/project/-edit-instance-action.emblem b/app/templates/project/-edit-instance-action.emblem new file mode 100644 index 0000000..af93eaf --- /dev/null +++ b/app/templates/project/-edit-instance-action.emblem @@ -0,0 +1,147 @@ +table.ui.top.attached.compact.definition.table + tr + td= t 'models.workflow_action_definition' + td= model.workflow_action_definition.title + tr + td= t 'attributes.project_instance_action.state' + td= state-badge model.state + if model.target_date + tr + td= t 'attributes.project_instance_action.target_date' + td= format-date model.target_date format=globals.date_formatting + tr + td= t 'models.project_instance' + td + = model.project_instance.client_identifier + if model.project_instance.reference_date + ' + span.between-brackets= format-date model.project_instance.reference_date format=globals.date_formatting + ' + = state-badge model.project_instance.state basic=true + tr + td= t 'models.workflow_group' + td + = model.workflow_action_definition.workflow_group.name + ' + = state-badge model.project_instance_group.state basic=true + if model.notes + tr + td= t 'attributes.project_instance_action.notes' + td= markdown-to-html model.notes + if model.workflow_action_definition.extra_info + tr + td= t 'attributes.workflow_action_definition.extra_info' + td= markdown-to-html model.workflow_action_definition.extra_info + if model.overdue_at + tr + td= t 'attributes.project_instance_action.overdue_at' + td= format-date model.overdue_at format=globals.time_formatting + if model.completed_at + tr + td= t 'attributes.project_instance_action.completed_at' + td= format-date model.completed_at format=globals.time_formatting + each model.pointing_project_instance_action_connections as |pointing_connection| + if (eq pointing_connection.connection_type 'constraint') + tr.pointing-action.constraint class=pointing_connection.project_instance_action.completed:completed:uncompleted + td= pointing_connection.project_instance_action.workflow_action_definition.title + td= project/instance-action-value-display project_instance_action=pointing_connection.project_instance_action save=(action 'open_current_modal') + else + tr.pointing-action class=pointing_connection.connection_type + td= pointing_connection.project_instance_action.workflow_action_definition.title + td= project/instance-action-value-display project_instance_action=pointing_connection.project_instance_action save=(action 'open_current_modal') +if (eq value_type 'text') + .ui.bottom.attached.segment + = textarea value=text_value +/minimal space for calendar at bottom right +if misc_events.length + .ui.horizontal.divider events + table.ui.striped.table + each misc_events as |event| + tr + td= event.source + td= event.event_key + td= event.event_value + td= format-date event.event_timestamp format=globals.time_formatting + +.ui.horizontal.divider notes +table.ui.top.attached.compact.definition.table + each note_events as |note_event| + tr + td.collapsing + = note_event.source + br + = format-date note_event.event_timestamp format=globals.time_formatting + td: pre= note_event.event_value +.ui.attached.segment= textarea value=new_note_content rows=3 +.ui.bottom.attached.segment + if can_edit_action + unless (contains model.state globals.project_instance_action.final_states) + button.ui.pink.button.pull-right click='start_inquiry' disabled=new_note_content_is_blank: span= t 'action_list.start_inquiry' + button.ui.primary.button click="add_new_note" disabled=new_note_content_is_blank: span= t 'action_list.add_note' + +.modal-actions.sticky + /button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + /button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + if can_edit_action + if has_uncompleted_constraints + .pull-right.ui.negative.message= t 'project_instance_action.has_uncompleted_constraints_message' + else + if (eq model.state 'inquiry') + button.ui.pink.button.pull-right{action 'continue_from_inquiry'} Continue… + else + if (eq value_type 'boolean') + if model.boolean_value + button.ui.yellow.button.pull-right{action 'set_value' 'boolean_value' false}= t 'project_instance_action.revoke' + else + button.ui.primary.button.pull-right{action 'set_value' 'boolean_value' true}= t 'project_instance_action.done' + else if (eq value_type 'date') + .pull-right: .ui.revoke.action.input + = ui-calendar-moment type='date' momentValue=model.date_value onChange=(action 'set_value' 'date_value') placeholder="Select date" popupOptions=(hash position='top right' maxSearchDepth=0) + if model.date_value + button.ui.yellow.button.pull-right{action 'set_value' 'date_value' null}= t 'project_instance_action.revoke' + else if (eq value_type 'timestamp') + if has_changed_timestamp_value + button.ui.primary.icon.button.pull-right{action 'set_timestamp_value'}: i.save.icon + if model.timestamp_value + button.ui.yellow.button.pull-right{action 'set_value' 'timestamp_value' null}= t 'project_instance_action.revoke' + .pull-right: .ui.revoke.action.input + = ui-calendar-moment type='datetime' ampm=false momentValue=timestamp_value onChange=(action (mut timestamp_value)) placeholder="Select timestamp" popupOptions=(hash position='top right' maxSearchDepth=0) + else if (eq value_type 'string') + .pull-right: .ui.revoke.action.input + = input value=string_value enter='set_string_value' + if (not-eq string_value model.string_value) + button.ui.primary.icon.button{action 'set_string_value'}: i.save.icon + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'number') + .pull-right: .ui.revoke.action.input + = number-input numericValue=number_value step=1 enter='set_number_value' + if (not-eq number_value model.number_value) + button.ui.primary.icon.button{action 'set_number_value'}: i.save.icon + if model.number_value + button.ui.yellow.button.pull-right{action 'set_value' 'number_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'select') + if model.workflow_action_definition.value_type_select_options.length + .ui.buttons.pull-right + = ui-dropdown class='button' selected=string_value onChange=(action 'set_value' 'string_value') direction='upward' + .default.text select… + i.dropdown.icon + .upward.menu + each model.workflow_action_definition.value_type_select_options as |option| + .item= option + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + + else + .pull-right: .ui.revoke.action.input + = input value=string_value enter='set_string_value' + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'text') + .ui.buttons.pull-right + if (not-eq text_value model.text_value) + button.ui.primary.button{action 'set_text_value'}= t 'project_instance_action.done' + if model.text_value + button.ui.yellow.button{action 'set_value' 'text_value' ''}= t 'project_instance_action.revoke' + + button.ui.basic.button.modal-rollback{action "close"}= t 'general.close' diff --git a/app/templates/project/-edit-project-scenario.emblem b/app/templates/project/-edit-project-scenario.emblem new file mode 100644 index 0000000..847a0f2 --- /dev/null +++ b/app/templates/project/-edit-project-scenario.emblem @@ -0,0 +1,20 @@ +table.ui.definition.table + tr + td= t 'attributes.project_scenario.name' + td= input value=model.name enter='saveRecord' +button.ui.button{action 'check_all'} Check all +button.ui.button{action 'check_none'} Check none +.workflow-instance-workflow-groups-container + each model.project.sorted_workflow_groups as |workflow_group| + .workflow-instance-workflow-group-container + .workflow-instance-workflow-group-title + ui-checkbox class='toggle' label=workflow_group.name checked=(contains workflow_group.id selected_workflow_group_ids) onChange=(action 'change_workflow_group_selection' workflow_group) + .workflow-instance-workflow-group-body + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + .workflow-action-definition-row + ui-checkbox class='toggle' label=workflow_action_definition.title checked=(contains workflow_action_definition.id selected_workflow_action_definition_ids) onChange=(action 'change_workflow_action_definition_selection' workflow_action_definition) + +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/project/-edit-project.emblem b/app/templates/project/-edit-project.emblem new file mode 100644 index 0000000..69d9ee1 --- /dev/null +++ b/app/templates/project/-edit-project.emblem @@ -0,0 +1,99 @@ +/.form-row + .form-label= t 'attributes.project.name' + .form-field= input value=model.name enter='saveRecord' +/.form-row + .form-label= t 'attributes.project.start_date' + .form-field= pick-a-date date=model.start_date on-selected=(action (mut model.start_date)) +/.form-row + .form-label= t 'attributes.project.end_date' + .form-field= pick-a-date date=model.end_date on-selected=(action (mut model.end_date)) +/.form-row + .form-label= t 'attributes.project.instance_scope' + .form-field + x-select value=model.instance_scope action=(action (mut model.instance_scope)) as |xs| + each globals.project.instance_scope_options as |instance_scope| + xs.option value=instance_scope + = instance_scope +/.form-row + .form-label= t 'attributes.project.expected_number_of_workflow_instances' + .form-field= number-input numericValue=model.expected_number_of_workflow_instances +/.form-row + .form-label= t 'models.plural.user' + .form-field= ui-multi-select options=possible_users labelProperty='nickname' selected=project_users +/.form-row + .form-label= t 'models.workflow_instance_type' + .form-field + x-select value=model.workflow_instance_type.content on-change=(action 'selectWorkflowInstanceType') as |xs| + option -- + each possible_workflow_instance_types as |record| + xs.option value=record + = record.name +table.ui.compact.definition.table + tr + td= t 'attributes.project.name' + td= input value=model.name enter='saveRecord' + tr + td= t 'attributes.project.start_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.start_date) onChange=(action (mut model.start_date)) + tr + td= t 'attributes.project.end_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.end_date) onChange=(action (mut model.end_date)) + tr + td= t 'attributes.project.expected_number_of_workflow_instances' + td= number-input numericValue=model.expected_number_of_workflow_instances + /tr + td= t 'models.plural.user' + td= ui-multi-select options=possible_users labelProperty='nickname' selected=project_users + tr + td= t 'models.workflow_instance_type' + td + x-select value=model.workflow_instance_type.content on-change=(action 'selectWorkflowInstanceType') as |xs| + option -- + each possible_workflow_instance_types as |record| + xs.option value=record + = record.name + tr + td= t 'attributes.project.instance_scope' + td + x-select value=model.instance_scope action=(action (mut model.instance_scope)) as |xs| + each globals.project.instance_scope_options as |instance_scope| + xs.option value=instance_scope + = instance_scope + if model.instance_scope_batch + tr + td= t 'attributes.project.batch_size_lower_value' + td= number-input numericValue=model.batch_size_lower_value + tr + td= t 'attributes.project.batch_size_upper_value' + td= number-input numericValue=model.batch_size_upper_value + tr + td= t 'attributes.project.reference_date_day_of_week' + td + = ui-dropdown class='selection' selected=model.reference_date_day_of_week onChange=(action (mut model.reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + .item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' + +unless model.isNew + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newProjectScenario' description=(t 'action.add_model' modelPath='models.project_scenario') + if model.project_scenarios.length + = push-action type='raster' action=(route-action 'show_project_scenarios_grid' model) + = t 'models.plural.project_scenario' + .ui.bottom.attached.segment + .ui.segments + each (sort-by 'name' model.project_scenarios) as |project_scenario| + project/scenario-edit-item model=project_scenario + else + .ui.segment No scenarios defined +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + if model.isNew + button.ui.secondary.button{action (route-action 'new_project_from_contract' model)}= t 'project.new_project_from_contract.title' diff --git a/app/templates/project/-edit-projects-context.emblem b/app/templates/project/-edit-projects-context.emblem new file mode 100644 index 0000000..2754674 --- /dev/null +++ b/app/templates/project/-edit-projects-context.emblem @@ -0,0 +1,12 @@ +.ui.container + .ui.visible.message= t 'project.modals.edit_projects_context.message' + h3.ui.top.attached.header + .ui.right.floated.icon.buttons= push-action type="new" action="newWorkflowInstanceType" + = t 'models.plural.workflow_instance_type' + .ui.bottom.attached.segment + .ui.segments + each workflow_instance_types as |workflow_instance_type| + .ui.segment + editable-attribute model=workflow_instance_type +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{ action "close"}= t 'general.close' diff --git a/app/templates/project/-edit-workflow-group.emblem b/app/templates/project/-edit-workflow-group.emblem new file mode 100644 index 0000000..e101193 --- /dev/null +++ b/app/templates/project/-edit-workflow-group.emblem @@ -0,0 +1,121 @@ +/.form-row + .form-label= t 'attributes.workflow_group.name' + .form-field= input value=model.name enter="saveRecord" + /.small-12.medium-9.columns= input value=model.name +/.form-row + .form-label= t 'attributes.workflow_group.offset_value' + .form-field + .ui.right.labeled.action.input + = number-input numericValue=model.offset_value step=1 + /button.ui.grey.icon.button{action (mut model.offset_value)}: i.remove.icon + .ui.label days +/.form-row + .form-label= t 'attributes.workflow_group.duration_value' + .form-field + .ui.right.labeled.action.input + = number-input numericValue=model.duration_value step=1 + /button.ui.grey.icon.button{action (mut model.duration_value)}: i.remove.icon + .ui.label days +table.ui.definition.table + tr + td= t 'attributes.workflow_group.name' + td= input value=model.name enter="saveRecord" + tr + td= t 'attributes.workflow_group.offset_type' + td + .ui.buttons + .ui.button class=offset_type_none:active click="'set_offset_type' 'none'" + = t 'workflow_group.offset_type.none.label' + .or + .ui.button class=offset_type_connected_finish:active click="'set_offset_type' 'connected_finish'" + = t 'workflow_group.offset_type.connected_finish.label' + /.or + /.ui.button class=offset_type_reference_date:active click="'set_offset_type' 'reference_date'" + = t 'workflow_group.offset_type.reference_date.label' + .or + .ui.button class=offset_type_connected_start:active click="'set_offset_type' 'connected_start'" + = t 'workflow_group.offset_type.connected_start.label' + /.inline.fields + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_none onChange=(action 'set_offset_type' 'none') label=(t 'workflow_group.offset_type.none.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_connected_finish onChange=(action 'set_offset_type' 'connected_finish') label=(t 'workflow_group.offset_type.connected_finish.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_reference_date onChange=(action 'set_offset_type' 'reference_date') label=(t 'workflow_group.offset_type.reference_date.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_connected_start onChange=(action 'set_offset_type' 'connected_start') label=(t 'workflow_group.offset_type.connected_start.label') + unless offset_type_none + tr + td + = t model.offset_type scope='workflow_group.offset_type' suffix='offset_value_label' + /= t 'attributes.workflow_group.offset_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.offset_value step=1 + /button.ui.grey.icon.button{action (mut model.offset_value)}: i.remove.icon + .ui.label= t model.duration_unit scope='date.unit.plural' + tr + td + = t model.offset_type scope='workflow_group.offset_type' suffix='offset_base_label' + /= t 'attributes.workflow_group.offset_base' + td + x-select value=model.offset_base.content on-change=(action (mut model.offset_base)) as |xs| + option X + each offset_selectable_workflow_groups as |workflow_group| + unless (eq workflow_group.id model.id) + xs.option value=workflow_group + span= t model.offset_type scope='workflow_group.offset_type' suffix='offset_base_prefix' + span= workflow_group.name + tr + td= t 'attributes.workflow_group.duration_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.duration_value step=1 + /button.ui.grey.icon.button{action (mut model.duration_value)}: i.remove.icon + .ui.label= t model.duration_unit scope='date.unit.plural' + if model.project.instance_scope_batch + tr + td= t 'attributes.workflow_group.batch_element_threshold_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.batch_element_threshold_value step=1 + if model.batch_element_threshold_value + button.ui.grey.icon.button{action (mut model.batch_element_threshold_value)}: i.remove.icon + /.ui.basic.dropdown.button + = ui-dropdown class='selection' selected=model.batch_element_threshold_unit onChange=(action 'set_batch_element_threshold_unit') + /.default.text= t model.batch_element_threshold_unit scope='date.unit.plural' bare=true + .text + i.dropdown.icon + .menu + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + .item data-value=batch_element_threshold_unit + span per + = t batch_element_threshold_unit scope='date.unit' + /.ui.label + /= t model.batch_element_threshold_unit scope='date.unit.plural' + /x-select value=model.batch_element_threshold_unit on-change=(action (mut model.batch_element_threshold_unit)) as |xs| + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + xs.option value=batch_element_threshold_unit + = t batch_element_threshold_unit scope='date.unit.plural' +unless model.isNew + each organization_tracker_subjects as |tracker_subject| + .form-row + .form-label= tracker_subject.model.name + .form-field + x-select value=tracker_subject.current_tracker_group on-change=(action (mut tracker_subject.current_tracker_group)) as |xs| + option -- + each tracker_subject.model.tracker_groups as |tracker_group| + xs.option value=tracker_group + = tracker_group.name + /h3 + = push-action type="new" action="newConstraint" class="right" + = t 'models.plural.workflow_group_constraint' + /.clearfix + /each model.constraints as |constraint| + workflow-group-edit-constraint constraint=constraint constraint_workflow_groups=constraint_workflow_groups + +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{ action "saveRecord"}= t 'general.save' + unless model.isNew + button.ui.negative.button.modal-destroy.pull-right{ action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{ action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/project/-load-project-instances.emblem b/app/templates/project/-load-project-instances.emblem new file mode 100644 index 0000000..a78333b --- /dev/null +++ b/app/templates/project/-load-project-instances.emblem @@ -0,0 +1,76 @@ +if loading_message + .ui.info.icon.message + i.notched.circle.loading.icon + .content= loading_message +if finished_message + .ui.positive.icon.message + i.flag.checkered.icon + .content= finished_message +unless (or loading_message finished_message) + if model.instance_scope_batch + .ui.top.attached.segment + .ui.right.floated.icon.buttons + = file-upload name='instance-list' onfileadd=(action 'load_instance_list') + a.ui.button id='upload-image' tabindex=0 + i.file.icon + span Upload CSV + .header + h3 Batch files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Batch identifier, + td Batch date, + td Scenario, + td Batch size + tbody + tr + td Ams-AAC, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1a, + td 700 + else + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Instance files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Instance identifier, + td Instance date, + td Scenario + td Global instance identifier + tbody + tr + td CFK42664, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1b + td CFK42664, + tr + td CFK42665, + td 2017-11-25, + td e585c580-2a48-4fcd-a852-63f92c6a9f1c + td CFK42665, + tr + td CDQ87810, + td , + td , + td + + if model.project_scenarios.length + .ui.top.attached.header + = t 'models.project_scenario' + span  identifiers + table.ui.definition.bottom.attached.table + tbody + each model.project_scenarios as |project_scenario| + tr + td= project_scenario.name + td= project_scenario.identifier +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{action 'close'}= t 'general.close' diff --git a/app/templates/project/-new-project-from-contract.emblem b/app/templates/project/-new-project-from-contract.emblem new file mode 100644 index 0000000..4fd6fb3 --- /dev/null +++ b/app/templates/project/-new-project-from-contract.emblem @@ -0,0 +1,17 @@ +.form-row + .form-label= t 'attributes.project.name' + .form-field= validated-input attribute='name' changeset=changeset enter='saveRecord' +.form-row + .form-label= t 'attributes.project.contract' + .form-field + = file-upload name='contract' onfileadd=(action "set_contract") + a.ui.button id="upload-contract" tabindex=0 + i.upload.icon + span Upload contract +if contract + = format-json value=contract top_title='Contract' +.modal-actions.sticky + if contract + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + diff --git a/app/templates/project/-position-workflow-action-definition-in-project.emblem b/app/templates/project/-position-workflow-action-definition-in-project.emblem new file mode 100644 index 0000000..ed5a015 --- /dev/null +++ b/app/templates/project/-position-workflow-action-definition-in-project.emblem @@ -0,0 +1,26 @@ +workflow-action-definition/modal-header model=model +.ui.two.column.grid + each model.project.sorted_workflow_groups as |workflow_group| + .column + .ui.top.attached.block.header: .content= workflow_group.name + .ui.bottom.attached.segment + .ui.list + unless (and (eq model.workflow_group.id workflow_group.id) (eq model.position 0)) + .item + button.ui.tiny.button{action 'position_workflow_action_definition_at_top_of' model workflow_group} + i.right.arrow.icon + span   place at the top + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + .item + .name= workflow_action_definition.name + .target + i.vertically.flipped.share.icon + if (eq workflow_action_definition.id model.id) + button.ui.tiny.button disabled=true self + else + button.ui.tiny.button{action 'position_workflow_action_definition_below' model workflow_action_definition} + span place at this position + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + button.ui.negative.button.pull-right{action 'unassign_workflow_action_definition'} Unassign diff --git a/app/templates/project/-project-scenarios-show-grid.emblem b/app/templates/project/-project-scenarios-show-grid.emblem new file mode 100644 index 0000000..2729529 --- /dev/null +++ b/app/templates/project/-project-scenarios-show-grid.emblem @@ -0,0 +1,28 @@ +with (sort-by 'name' model.project_scenarios) as |project_scenarios| + table.ui.definition.table + thead + tr + th + each project_scenarios as |project_scenario| + th: a.pointable{action 'edit_project_scenario' project_scenario}= project_scenario.name + tbody + each model.sorted_workflow_groups as |workflow_group| + tr.active + td= workflow_group.name + each project_scenarios as |project_scenario| + td + if (contains workflow_group project_scenario.workflow_groups) + i.square.icon + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + tr + td= workflow_action_definition.title + each project_scenarios as |project_scenario| + td + if (contains workflow_action_definition project_scenario.workflow_action_definitions) + i.circle.icon + + + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + diff --git a/app/templates/project/index.emblem b/app/templates/project/index.emblem new file mode 100644 index 0000000..b601f1f --- /dev/null +++ b/app/templates/project/index.emblem @@ -0,0 +1,24 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.project' + +.ui.container + h3.ui.top.attached.header + if (can 'manage' 'model' 'panda/project') + .ui.right.floated.icon.buttons + = push-action type='edit' action='editProjectsContext' + = push-action type='new' action='newProject' description='Add project' + = t 'models.plural.project' + table.ui.unstackable.bottom.attached.table + /thead + tr + th= t 'attributes.project.name' + th.actions + tbody + each (sort-by 'name' model) as |project| + tr + td= link-to project.name 'project.show' project + td= project.workflow_instance_type.name + /td.actions= push-action type='edit' action='editProject' model=project + +/button.ui.secondary.button{action (route-action 'new_project_from_contract')}= t 'project.modals.new_project_from_contract.title' diff --git a/app/templates/project/show/-dunlop-project-info.emblem b/app/templates/project/show/-dunlop-project-info.emblem new file mode 100644 index 0000000..f238993 --- /dev/null +++ b/app/templates/project/show/-dunlop-project-info.emblem @@ -0,0 +1,40 @@ +.ui.top.attached.two.item.stackable.tabs.menu + .item.pointable click="'set_tab' 'index'" class="{{if (eq tab 'index') 'active'}}" + a Offsets + .item.pointable click="'set_tab' 'identifiers'" class="{{if (eq tab 'identifiers') 'active'}}" + a Identifiers +if (eq tab 'index') + table.ui.bottom.attached.compact.celled.table + thead + tr + th= t 'models.workflow_group' + th= t 'models.workflow_action_definition' + th Offset + tbody + each workflow_action_definition_nodes as |workflow_action_definition_node| + tr + td= workflow_action_definition_node.record.workflow_group.name + td= workflow_action_definition_node.record.title + td= workflow_action_definition_node.absolute_value +if (eq tab 'identifiers') + table.ui.bottom.attached.compact.definition.table + thead + tr + th + th= t 'attributes.workflow_action_definition.identifier' + th= t 'attributes.workflow_action_definition.completes_group' + tbody + each model.sorted_workflow_groups as |workflow_group| + tr.active + td.collapsing= workflow_group.name + td= workflow_group.identifier + td + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + tr + td= workflow_action_definition.title + td= workflow_action_definition.identifier + td + if workflow_action_definition.completes_group + i.checkmark.box.icon +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' diff --git a/app/templates/project/show/add-progress.emblem b/app/templates/project/show/add-progress.emblem new file mode 100644 index 0000000..d7a7838 --- /dev/null +++ b/app/templates/project/show/add-progress.emblem @@ -0,0 +1,79 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.add_progress.title' +.ui.container + .ui.dividing.header + /.ui.right.floated.icon.buttons + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.add_progress.title' + + /if model.project_scenarios.length + .ui.top.attached.header + = t 'models.plural.project_scenario' + table.ui.definition.bottom.attached.table + tbody + each model.project_scenarios as |project_scenario| + tr + td= project_scenario.name + td= project_scenario.identifier + if model.instance_scope_batch + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button{action 'openModal' 'project/load-project-instances' (hash model=model)} + i.upload.icon + .header + h3 Batch files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Batch identifier, + td Batch date, + td Scenario, + td Batch size + tbody + tr + td Ams-AAC, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1a, + td 700 + else + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Instance files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Instance identifier, + td Instance date, + td Scenario + td Global instance identifier + tbody + tr + td CFK42664, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1b + td CFK42664, + tr + td CFK42665, + td 2017-11-25, + td e585c580-2a48-4fcd-a852-63f92c6a9f1c + td CFK42665, + tr + td CDQ87810, + td , + td , + td + diff --git a/app/templates/project/show/analysis.emblem b/app/templates/project/show/analysis.emblem new file mode 100644 index 0000000..7da6e99 --- /dev/null +++ b/app/templates/project/show/analysis.emblem @@ -0,0 +1,19 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.analysis.title' +.ui.container + .ui.header + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.analysis.title' + .ui.divider + br + br + = panda-image path="under-construction.svg" diff --git a/app/templates/project/show/dunlop-generator.emblem b/app/templates/project/show/dunlop-generator.emblem new file mode 100644 index 0000000..998b161 --- /dev/null +++ b/app/templates/project/show/dunlop-generator.emblem @@ -0,0 +1,42 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.dunlop_generator.title' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.dunlop_generator.title' + .ui.three.column.grid + .column + .title-box + .title-box-heading= t 'models.plural.source_file' + = ui-multi-select options=possible_source_files selection=selected_source_files + .column + .title-box + .title-box-heading Modules + .kbt-endpoint + .module-row + label + = input type='checkbox' checked=kbt_endpoint.active + span= t 'dunlop.module.kbt_endpoint' + if kbt_endpoint.active + h3 Special KBT config settings + .kbt-endpoint + .module-row + label + = input type='checkbox' checked=wba_mailbox.active + span= t 'dunlop.module.wba_mailbox' + if wba_mailbox.active + h3 Special WBA Mailbox config settings + + if activated_modules + h3.ui.header Geselecteerde modules + each activated_modules as |activated_module| + h5= dynamic-path-t 'dunlop.module' postpath=activated_module.name diff --git a/app/templates/project/show/dunlop.emblem b/app/templates/project/show/dunlop.emblem new file mode 100644 index 0000000..c4b10b9 --- /dev/null +++ b/app/templates/project/show/dunlop.emblem @@ -0,0 +1,93 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.dunlop.title' +.ui.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newWorkflowStep' + = push-action type='edit' action='editProject' model=model + = push-action type='sort' action='sortWorkflowSteps' + = push-action type='contract' action='projectContract' model=model + /= push-action type='show' action='displayWorkflowActionDefinitions' + = push-action type='show' action='show_project_info' + if model.project_scenarios.length + .project-scenarios-dropdown-container + / MULTIPLE INTERSECTIONABLE SCENARIOS + /.ui.small.buttons + = ui-dropdown class='multiple button' selected=selected_project_scenarios onChange=(action 'select_project_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .header + i.grid.layout.icon + a.pointable{action 'show_project_scenarios_grid' model} Show activation grid + each (sort-by 'name' model.project_scenarios) as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + if has_multiple_selected_project_scenarios + if (eq selected_project_scenarios_set_operation 'union') + button.ui.button.logical-icon-button{action (mut selected_project_scenarios_set_operation) 'intersection'} + img.logical-icon-image.logical-disjunction src="/panda/app/assets/icons/svg/logical-disjunction.svg" + else + button.ui.button.logical-icon-button{action (mut selected_project_scenarios_set_operation) 'union'} + img.logical-icon-image.logical-disjunction src="/panda/app/assets/icons/svg/logical-conjunction.svg" + if has_one_selected_project_scenario + button.ui.edit.icon.button{action 'edit_selected_project_scenario'} + i.edit.icon + = project/edit-active-project-scenario project=project active_project_scenario=active_project_scenario + i.sidebar.icon{action 'toggle_sidebar' 'sub-sidebar'} + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.dunlop.title' +.ui.divider +if model.workflow_groups.isSettled + .ui.pushable.component.context + ui-sidebar class='menu left very wide vertical' context='.component.context' id='sub-sidebar' + .header.item Unassigned + each model.unassigned_workflow_action_definitions as |workflow_action_definition| + .item data-workflow-action-definition=workflow_action_definition.id + .ui.right.floated.small.icon.buttons + = push-action type='random' action=(action 'positionWorkflowActionDefinitionInProject' workflow_action_definition) + = workflow_action_definition.title + .pusher + .workflow-instance-workflow-groups-container + each model.sorted_workflow_groups as |workflow_group| + if (contains workflow_group active_workflow_groups) + .workflow-instance-workflow-group-container + = workflow-group-header workflow_group=workflow_group + /= draggable-dropzone dropped='positionWorkflowActionDefinition' content=workflow_group class='project-workflow_group-dropzone' + .workflow-instance-workflow-group-body + /if workflow_group.constraints.length + .workflow-group-constraints-count + span.count= workflow_group.constraints.length + span= t 'models.plural.workflow_group_constraint' + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + /= draggable-item content=workflow_action_definition class='workflow_action_definition-item-container' + if (contains workflow_action_definition active_workflow_action_definitions) + = workflow-action-definition-item workflow_action_definition=workflow_action_definition + /.row: .small-12.columns= input value=newWorkflowStepName enter='newWorkflowStep' placeholder='New workflow_group name' + .row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_offset' + = project/workflow-action-offsets-graph project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + .row= collapsible-content 'Workflow group relative to migration date lead times diagram' + = project/workflow-group-offset-diagram project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_groups=active_workflow_groups active_project_scenario=active_project_scenario + .row= collapsible-content 'Action execution relative to migration date offsets diagram' + = project/action-execution-offset-diagram project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + .row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_constraint' + = project/workflow-action-constraints-graph project=model active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + /.row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_offset' collapsed=false + = project/workflow-action-offsets-graph-old3 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions + /.row= collapsible-content 'Old 2 Action execution relative to migration date offsets diagram' + = project/action-execution-offset-diagram-old2 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week + /.row= collapsible-content 'Old 2 action offset diagram' + = project/workflow-action-offsets-graph-old2 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week + /.row= collapsible-content 'Old 1 action offset diagram' + = project/workflow-action-offsets-graph-old1 project=model + /.row: .small-12.columns= project-workflow-groups-graph project=model +else + .row: .small-12.columns: span.fa.fa-3x.fa-spinner.fa-spin diff --git a/app/templates/project/show/index.emblem b/app/templates/project/show/index.emblem new file mode 100644 index 0000000..5b92eb5 --- /dev/null +++ b/app/templates/project/show/index.emblem @@ -0,0 +1,50 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item active=true + span= t 'models.project' + span : + = model.name +.ui.container + .ui.header + .ui.right.floated.icon.buttons + if (can 'manage' 'model' 'panda/project') + = push-action type='edit' action='editProject' model=model + /= push-action type='sort' action='sortWorkflowSteps' + /= push-action type='show' action='displayWorkflowActionDefinitions' + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + .ui.segment + if (can 'manage' 'model' 'panda/project') + h2.ui.header + i.grid.layout.icon + .content= link-to 'project.show.dunlop' model: span= t 'project.dunlop.title' + h2.ui.header + i.bar.chart.icon + .content= link-to 'project.show.analysis' model: span= t 'project.analysis.title' + /ul.ui.list + li= link-to 'project.show.dunlop' model + span= t 'project.dunlop.title' + li= link-to 'project.show.dunlop-generator' model + span= t 'project.dunlop_generator.title' + li= link-to 'project.show.add-progress' model + span= t 'project.add_progress.title' + if (can 'manage' 'model' 'panda/project-instance') + .ui.segment + .ui.three.column.stackable.grid + .column= link-to 'project.show.project-instance' model class='ui fluid button' + i.list.icon + span= t 'models.plural.project_instance' + .column= link-to 'project.show.project-instance-group' model class='ui fluid button' + i.list.icon + span= t 'models.plural.project_instance_group' + .column= link-to 'project.show.project-instance-action' model class="ui fluid button" + i.list.icon + span= t 'models.plural.project_instance_action' + /li= link-to 'project.show.tracker-subjects' model + span= t 'models.plural.tracker_subject' + ul + each tracker_subjects as |tracker_subject| + li= link-to 'project.show.tracker-subject' model.id tracker_subject.id: span= tracker_subject.name + diff --git a/app/templates/project/show/project-instance-action/index.emblem b/app/templates/project/show/project-instance-action/index.emblem new file mode 100644 index 0000000..e1c0d47 --- /dev/null +++ b/app/templates/project/show/project-instance-action/index.emblem @@ -0,0 +1,87 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' project: span= project.name + breadcrumb.item active=true + span= t 'models.plural.project_instance_action' +.ui.right.floated.pagination.menu= page-numbers content=filtered_project_instance_actions showFL=true += page-title 'models.plural.project_instance_action' + = pagination-progress content=filtered_project_instance_actions class='between-brackets' +table.ui.stiped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'models.project_instance' + th= t 'attributes.project_instance_action.state' + / ui-dropdown class='right basic labeled icon' action='nothing' + i.dropdown.icon + span.ui.tiny.header class=filter.state_in.length:active-filter + = t 'attributes.project_instance_action.state' + .menu + each globals.project_instance_action.states as |state| + ui-checkbox class='item' label=state onChange=(action 'toggle_filter' 'state_in' state) + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + table-filters content=model filters=filters as |filter| + = filter.dropdown-select key='project_instance_group_workflow_group_id' options=project.sorted_workflow_groups labelPath='name' + / ui-dropdown class='right basic labeled icon' action='nothing' + i.dropdown.icon + span.ui.tiny.header class=filter.project_instance_group_workflow_group_id_in.length:active-filter + = t 'models.project_instance_group' + .menu + each project.sorted_workflow_groups as |workflow_group| + ui-checkbox class='item' label=workflow_group.name onChange=(action 'toggle_filter' 'project_instance_group_workflow_group_id_in' workflow_group.id) + th class=filters.workflow_action_definition_id_in.length:active-filter + ui-dropdown class='right basic labeled icon' action='nothing' fullTextSearch=true + /i.dropdown.icon + /== workflow_action_definitions_filter_html + if filters.workflow_action_definition_id_in.length + ui-popup html=(join '

' (map-by 'title' filter_workflow_action_definitions)) position='bottom center' + span= filters.workflow_action_definition_id_in.length + ' + if (eq filters.workflow_action_definition_id_in.length 1) (t 'models.workflow_action_definition') (t 'models.plural.workflow_action_definition') + else + i.filter.icon + .menu + .ui.icon.search.input + i.search.icon + input type='text' name='search' placeholder='Search…' + .scrolling.menu + each project.workflow_action_definitions as |workflow_action_definition| + ui-checkbox class='item' checked=(contains workflow_action_definition.id filters.workflow_action_definition_id_in) label=workflow_action_definition.title onChange=(action 'toggle_filter' 'workflow_action_definition_id_in' workflow_action_definition.id) + if filters.workflow_action_definition_id_in.length + i.remove.icon{action 'clear_filter' 'workflow_action_definition_id_in'} + = filter.input key='project_instance_client_identifier_cont' + = filter.dropdown-select key='state' options=globals.project_instance_action.states translationPath='attributes.state' + filter.date-range key='target_date' + /th class=date_filter_active:active-filter + ui-dropdown class='right basic labeled top right pointing icon' action='nothing' + = date_filter_text + .menu + .item= ui-calendar-moment type='date' momentValue=filter.date_gteq onChange=(action (mut filter.date_gteq)) allowClear=true + .divider + .item= ui-calendar-moment type='date' momentValue=filter.date_lteq onChange=(action (mut filter.date_lteq)) allowClear=true + .divider + .item + button.ui.primary.button{action 'set_filter_date_today'}= t 'date.today' + button.ui.primary.button{action 'set_filter_date_this_week'}= t 'date.this_week' + if date_filter_active + i.remove.icon{action 'clear_filter' 'date_gteq' 'date_lteq'} + th + tbody + each filtered_project_instance_actions as |project_instance_action| + tr + td + if project_instance_action.project_instance_group.id + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + span= project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td= link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + span= project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action.state + td= format-date project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action diff --git a/app/templates/project/show/project-instance-group/index.emblem b/app/templates/project/show/project-instance-group/index.emblem new file mode 100644 index 0000000..cc98ef5 --- /dev/null +++ b/app/templates/project/show/project-instance-group/index.emblem @@ -0,0 +1,35 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'models.plural.project_instance_group' +table.ui.stiped.table + thead + tr + th + = t 'models.workflow_group' + /ui-dropdown class='right basic labeled icon button' action='nothing' + i.dropdown.icon + span.ui.tiny.header Filter + .menu + .ui.icon.search.input + i.search.icon + input type='text' name='search' placeholder='Search…' + .scrolling.menu + each model.project.workflow_groups as |workflow_group| + ui-checkbox class='item' label=workflow_group.title onChange=(action 'toggle_workflow_group_filter' workflow_action_definition) + + th= t 'models.project_instance' + th= t 'attributes.project_instance_group.state' + tbody + each model.project_instance_groups as |project_instance_group| + tr + td= link-to 'project.show.project-instance-group.show' project_instance_group.project_instance.project project_instance_group + span= project_instance_group.workflow_group.name + td= link-to 'project.show.project-instance.show' project_instance_group.project_instance.project.id project_instance_group.project_instance.id + span= project_instance_group.project_instance.client_identifier + td= state-badge project_instance_group.state diff --git a/app/templates/project/show/project-instance-group/show.emblem b/app/templates/project/show/project-instance-group/show.emblem new file mode 100644 index 0000000..6d64fd3 --- /dev/null +++ b/app/templates/project/show/project-instance-group/show.emblem @@ -0,0 +1,29 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project_instance.project: span= model.project_instance.project.name + breadcrumb.item + span= t 'models.project_instance' + span : + = link-to 'project.show.project-instance.show' model.project_instance.project model.project_instance: span= model.project_instance.client_identifier + breadcrumb.item active=true + span= t 'models.project_instance_group' + span : + span= model.workflow_group.name + +.ui.top.attached.header= t 'models.plural.project_instance_action' +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.workflow_action_definition' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.target_date' + tbody + each (sort-by (route-action 'dateSort' 'target_date') model.project_instance_actions) as |project_instance_action| + tr + td= project_instance_action.workflow_action_definition.title + td= state-badge project_instance_action.state + td= date project_instance_action.target_date diff --git a/app/templates/project/show/project-instance/-new-project-instance.emblem b/app/templates/project/show/project-instance/-new-project-instance.emblem new file mode 100644 index 0000000..d20d567 --- /dev/null +++ b/app/templates/project/show/project-instance/-new-project-instance.emblem @@ -0,0 +1,45 @@ +table.ui.compact.definition.table + tbody + tr + td= t 'attributes.project_instance.client_identifier' + td= validated-input attribute='client_identifier' changeset=model.changeset + tr + td= t 'models.project_scenario' + td + = ui-dropdown class='selection' selected=model.changeset.project_scenario onChange=(action 'set_project_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each model.project.sorted_project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + tr + td= t 'attributes.project_instance.reference_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.changeset.reference_date) onChange=(action 'set_reference_date') placeholder='X' + if model.project.instance_scope_batch + tr + td= t 'attributes.project_instance.batch_size' + td= number-input numericValue=model.changeset.batch_size +if dependencies_loading + .ui.active.loader +else + .ui.bulleted.list + table.ui.table + tbody + each (sort-by 'workflow_group.position' new_project_instance_groups) as |project_instance_group| + tr.active + td: strong= project_instance_group.workflow_group.name + td.actions + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + tr + td= project_instance_action.workflow_action_definition.title + td.actions= date project_instance_action.date +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + /.ui.buttons + button.ui.secondary.button{action 'calculate_workflow_based_instances'} Calculate workflow based instances + /button.ui.secondary.icon.button: i.close.icon diff --git a/app/templates/project/show/project-instance/index.emblem b/app/templates/project/show/project-instance/index.emblem new file mode 100644 index 0000000..35cf019 --- /dev/null +++ b/app/templates/project/show/project-instance/index.emblem @@ -0,0 +1,78 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' project: span= project.name + breadcrumb.item active=true + span= t 'models.plural.project_instance' +.ui.right.floated.pagination.menu= page-numbers content=filtered_project_instances showFL=true += page-title 'models.plural.project_instance' + = pagination-progress content=filtered_project_instances class='between-brackets' +table.ui.table + thead + tr + th= t 'attributes.project_instance.client_identifier' + th= t 'attributes.project_instance.state' + th= t 'models.project_scenario' + th= t 'attributes.project_instance.reference_date' + if project.instance_scope_batch + th= t 'attributes.project_instance.batch_size' + th.actions + = push-action type='new' action=(route-action 'new_project_instance') + button.ui.small.icon.button{action 'openModal' 'project/load-project-instances' (hash model=project)} + i.upload.icon + table-filters content=model filters=filters as |filter| + = filter.input key='client_identifier_cont' + = filter.dropdown-select key='state' options=possible_states + = filter.dropdown-select key='project_scenario_id' options=project.sorted_project_scenarios labelPath='name' + = filter.date-range key='reference_date' + if project.instance_scope_batch + th + th= pagination-progress content=filtered_project_instances + + + /tr.filters + th + th + ui-dropdown class='right basic labeled icon' action='nothing' + /i.dropdown.icon + span.ui.tiny.header class=filter.project_scenario_id_in.length:active-filter + if filter.project_scenario_id_in.length + ui-popup html=(join '

' (map-by 'name' filter_project_scenarios)) position='bottom center' + span= filter.project_scenario_id_in.length + ' + if (eq filter.project_scenario_id_in.length 1) (t 'models.project_scenario') (t 'models.plural.project_scenario') + ' + i.remove.icon{action 'clear_filter' 'project_scenario_id_in'} + else + i.filter.icon + .menu + each project.sorted_project_scenarios as |project_scenario| + ui-checkbox class='item' checked=(contains project_scenario.id filter.project_scenario_id_in) label=project_scenario.name onChange=(action 'toggle_filter' 'project_scenario_id_in' project_scenario.id) + th + ui-dropdown class='right basic labeled top right pointing icon' action='nothing' + span.ui.tiny.header class=reference_date_filter_active:active-filter + = reference_date_filter_text + i.dropdown.icon + .menu + .item= ui-calendar-moment type='date' momentValue=filter.reference_date_gteq onChange=(action (mut filter.reference_date_gteq)) allowClear=true + .divider + .item= ui-calendar-moment type='date' momentValue=filter.reference_date_lteq onChange=(action (mut filter.reference_date_lteq)) allowClear=true + if project.instance_scope_batch + th + th.actions + + tbody + each filtered_project_instances as |project_instance| + tr + td + = link-to 'project.show.project-instance.show' project.id project_instance.id + = project_instance.client_identifier + td= state-badge project_instance.state + td= project_instance.project_scenario.name + td= date project_instance.reference_date + if project.instance_scope_batch + td= project_instance.batch_size + td.actions diff --git a/app/templates/project/show/project-instance/show.emblem b/app/templates/project/show/project-instance/show.emblem new file mode 100644 index 0000000..04ad567 --- /dev/null +++ b/app/templates/project/show/project-instance/show.emblem @@ -0,0 +1,37 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project: span= model.project.name + breadcrumb.item + = link-to 'project.show.project-instance.index' model.project.id + span= t 'models.plural.project_instance' + breadcrumb.item active=true + span= t 'models.project_instance' + span : + span= model.client_identifier + span  →  + = breadcrumb_detail +table.ui.definition.table + tbody + tr + td= t 'attributes.project_instance.client_identifier' + td= model.client_identifier + tr + td= t 'attributes.project_instance.state' + td= state-badge model.state + tr + td= t 'models.project_scenario' + td= model.project_scenario.name + tr + td= t 'attributes.project_instance.reference_date' + td= date model.reference_date +.ui.five.item.stackable.tabs.menu + = link-to 'project.show.project-instance.show.index' model.project model class='item': span= t 'project_instance.show.grid' + = link-to 'project.show.project-instance.show.list' model.project model class='item': span= t 'project_instance.show.list' + = link-to 'project.show.project-instance.show.groups' model.project model class='item': span= t 'models.plural.project_instance_group' + = link-to 'project.show.project-instance.show.actions' model.project model class='item': span= t 'models.plural.project_instance_action' + = link-to 'project.show.project-instance.show.graphs' model.project model class='item': span= t 'project_instance.show.graphs' += outlet diff --git a/app/templates/project/show/project-instance/show/actions.emblem b/app/templates/project/show/project-instance/show/actions.emblem new file mode 100644 index 0000000..730c531 --- /dev/null +++ b/app/templates/project/show/project-instance/show/actions.emblem @@ -0,0 +1,19 @@ +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + tbody + each (sort-by (route-action 'dateSort' 'target_date') model.project_instance_actions) as |project_instance_action| + tr + td + if project_instance_action.project_instance_group.id + = link-to 'project.show.project-instance-group.show' model.project project_instance_action.project_instance_group.id + span= project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td= state-badge project_instance_action.state + td= format-date project_instance_action.target_date format=globals.date_formatting + td= project/instance-action-value-display project_instance_action=project_instance_action diff --git a/app/templates/project/show/project-instance/show/graphs.emblem b/app/templates/project/show/project-instance/show/graphs.emblem new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/project/show/project-instance/show/groups.emblem b/app/templates/project/show/project-instance/show/groups.emblem new file mode 100644 index 0000000..94f8fa6 --- /dev/null +++ b/app/templates/project/show/project-instance/show/groups.emblem @@ -0,0 +1,12 @@ +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'attributes.project_instance_group.state' + tbody + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + tr + td + = link-to 'project.show.project-instance-group.show' model.project.id project_instance_group.id + span= project_instance_group.workflow_group.name + td= state-badge project_instance_group.state diff --git a/app/templates/project/show/project-instance/show/index.emblem b/app/templates/project/show/project-instance/show/index.emblem new file mode 100644 index 0000000..7a61432 --- /dev/null +++ b/app/templates/project/show/project-instance/show/index.emblem @@ -0,0 +1,15 @@ +.workflow-instance-workflow-groups-container + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + .workflow-instance-workflow-group-container class=project_instance_group.state + .workflow-instance-workflow-group-title= project_instance_group.workflow_group.name + .workflow-instance-workflow-group-body + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + .workflow-action-container class=project_instance_action.state + .ui.grid + .ten.wide.column= project_instance_action.workflow_action_definition.title + .six.wide.column + = project/instance-action-value-display project_instance_action=project_instance_action + /if (or (eq project_instance_action.workflow_action_definition.value_type 'boolean') (eq project_instance_action.workflow_action_definition.value_type 'number')) + / = project_instance_action.value_display + /else + / = ui-popup tagName="i" class="info circle icon" html=project_instance_action.value_display diff --git a/app/templates/project/show/project-instance/show/list.emblem b/app/templates/project/show/project-instance/show/list.emblem new file mode 100644 index 0000000..ce93352 --- /dev/null +++ b/app/templates/project/show/project-instance/show/list.emblem @@ -0,0 +1,12 @@ +.project-instance-list-tab-container + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + .project-instance-list-workflow-group-container + .project-instance-list-workflow-group-row + .project-instance-list-workflow-group= project_instance_group.workflow_group.name + .project-instance-list-workflow-step-context + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + .project-instance-list-workflow-action-definition-row + .project-instance-list-workflow-action-definition= project_instance_action.workflow_action_definition.title + .project-instance-list-workflow-action-context + = date project_instance_action.date + diff --git a/app/templates/project/show/tracker-subject.emblem b/app/templates/project/show/tracker-subject.emblem new file mode 100644 index 0000000..b6332d3 --- /dev/null +++ b/app/templates/project/show/tracker-subject.emblem @@ -0,0 +1,30 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project: span= model.project.name + breadcrumb.item active=true + span= t 'models.tracker_subject' + span : + = model.tracker_subject.name +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= link-to 'project.show' model.project: span= model.project.name + .ui.grid + each grouped_tracker_groups as |tracker_group_group| + .sixteen.wide.column + h5= link-to 'tracker-subject.show.tracker-group' model.tracker_subject.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject current_tracker_group=tracker_group_group.model + + h4 Uncategorized steps + ul + each uncategorized_workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject diff --git a/app/templates/project/show/tracker-subjects.emblem b/app/templates/project/show/tracker-subjects.emblem new file mode 100644 index 0000000..82589af --- /dev/null +++ b/app/templates/project/show/tracker-subjects.emblem @@ -0,0 +1,19 @@ +/ TODO not relevant for now +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + = t 'models.plural.tracker_subject' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + each tracker_subjects_for_project as |tracker_subject| + h4= link-to 'project.show.tracker-subject' model.id tracker_subject.id + span= tracker_subject.name + diff --git a/app/templates/tracker-subject/index.emblem b/app/templates/tracker-subject/index.emblem new file mode 100644 index 0000000..7ac02b1 --- /dev/null +++ b/app/templates/tracker-subject/index.emblem @@ -0,0 +1,20 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.tracker_subject' + +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons= push-action type='new' action='newTrackerSubject' + = t 'models.plural.tracker_subject' + /.ui.bottom.attached.segment + .ui.segments + each tracker_subjects_for_user as |tracker_subject| + .ui.segment + .ui.small.right.floated.icon.buttons= push-action type='edit' action='editTrackerSubject' model=tracker_subject + = link-to 'tracker-subject.show' tracker_subject: span= tracker_subject.name + table.ui.unstackable.bottom.attached.table + tbody + each model as |tracker_subject| + tr + td= link-to tracker_subject.name 'tracker-subject.show' tracker_subject + td.actions= push-action type='edit' action='editTrackerSubject' model=(changeset tracker_subject) diff --git a/app/templates/tracker-subject/show/index.emblem b/app/templates/tracker-subject/show/index.emblem new file mode 100644 index 0000000..73e7a81 --- /dev/null +++ b/app/templates/tracker-subject/show/index.emblem @@ -0,0 +1,36 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item active=true + span= t 'models.tracker_subject' + span : + = model.name + +.ui.container + .ui.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newTrackerGroup' + = push-action type='edit' action='editTrackerSubject' model=model + = push-action type='sort' action='sortTrackerGroups' + h2 + span.resource-descriptor= t 'models.tracker_subject' + span.resource-name= model.name + .ui.segment + ul.ui.list + li= link-to 'tracker-subject.show.tracking-connections' model.id: span Tracking + br +if model.tracker_groups.isSettled + .tracker-subject-tracker-groups-container + each model.sorted_tracker_groups as |tracker_group| + .tracker-subject-tracker-group-container + .tracker-subject-tracker-group-title + .ui.small.right.floated.icon.buttons + = push-action type='edit' action='editTrackerGroup' model=tracker_group class='right' + = push-action type='sort' action='sortTrackerItems' model=tracker_group class='right' + = push-action type='new' action='newTrackerItem' model=tracker_group class='right' + = tracker_group.name + .tracker-subject-tracker-group-body + each tracker_group.sorted_tracker_items as |tracker_item| + editable-attribute model=tracker_item +else + .row: .small-12.columns: span.fa.fa-3x.fa-spinner.fa-spin diff --git a/app/templates/tracker-subject/show/project.emblem b/app/templates/tracker-subject/show/project.emblem new file mode 100644 index 0000000..e8f6d7d --- /dev/null +++ b/app/templates/tracker-subject/show/project.emblem @@ -0,0 +1,29 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model.tracker_subject + = model.tracker_subject.name + breadcrumb.item active=true + span Project links: + span= model.project.name +.ui.container + .ui.header + /.ui.right.floated.icon.buttons + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.project.name + each grouped_tracker_groups as |tracker_group_group| + .row: .small-12.columns + h5= link-to 'tracker-subject.show.tracker-group' model.tracker_subject.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject current_tracker_group=tracker_group_group.model + h4 Uncategorized steps + ul + each uncategorized_workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject diff --git a/app/templates/tracker-subject/show/tracker-group.emblem b/app/templates/tracker-subject/show/tracker-group.emblem new file mode 100644 index 0000000..f9c54e7 --- /dev/null +++ b/app/templates/tracker-subject/show/tracker-group.emblem @@ -0,0 +1,23 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model + = model.name + breadcrumb.item active=true + span= t 'models.tracker_group' +.row + .small-12.columns + h2 + span.resource-descriptor= t 'models.tracker_group' + span.resource-name= model.name + each model.projects as |project| + h3= project.name +each grouped_projects as |project_group| + .row: .span-12.columns + h3= link-to 'tracker-subject.show.project' model.tracker_subject.id project_group.model.id: span= project_group.model.name + ul + each project_group.workflow_groups as |workflow_group| + li= workflow_group.name diff --git a/app/templates/tracker-subject/show/tracking-connections.emblem b/app/templates/tracker-subject/show/tracking-connections.emblem new file mode 100644 index 0000000..055847b --- /dev/null +++ b/app/templates/tracker-subject/show/tracking-connections.emblem @@ -0,0 +1,36 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model + = model.name + breadcrumb.item active=true + span Tracking connections +.ui.container + .ui.header + .ui.right.floated.icon.buttons= push-action type='edit' action='editTrackerSubject' model=model + h2 + span.resource-descriptor= t 'models.tracker_subject' + span.resource-name= model.name + + each grouped_tracker_groups as |tracker_group_group| + .row: .span-12.columns + h3= link-to 'tracker-subject.show.tracker-group' model.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.projects as |project| + li + /= link-to 'tracker-subject.project' model.id project.model.id: span= project.model.name + /= link-to 'project/tracker-subject' project.model.id model.id: span= project.model.name + = link-to 'tracker-subject.show.project' model.id project.model.id: span= project.model.name + ul + each project.workflow_groups as |workflow_group| + li= workflow_group.name + /each tracker_group_group.workflow_groups as |workflow_group| + .row: .span-12.columns: h4= tracker_group_group.model.name + .row + .small-6.columns= workflow_group.project.name + .small-6.columns= workflow_group.name +/= svg_content +/pre.hide= graph_content diff --git a/app/templates/user/-edit-user.emblem b/app/templates/user/-edit-user.emblem new file mode 100644 index 0000000..3208c36 --- /dev/null +++ b/app/templates/user/-edit-user.emblem @@ -0,0 +1,11 @@ +table.ui.compact.definition.table + tr + td= t 'attributes.user.nickname' + td= input value=model.nickname enter='saveRecord' + tr + td= t 'models.plural.organization' + td= ui-multi-select options=possible_organizations labelProperty='name' selected=selected_organizations +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/user/-new-user.emblem b/app/templates/user/-new-user.emblem new file mode 100644 index 0000000..0758675 --- /dev/null +++ b/app/templates/user/-new-user.emblem @@ -0,0 +1,14 @@ +table.ui.compact.definition.table + tr + td= t 'attributes.user.email' + td= validated-input attribute='email' enter=(action 'saveRecord' (hash trigger='email')) changeset=model.changeset + tr + td= t 'attributes.user.nickname' + td= validated-input attribute='nickname' enter=(action 'saveRecord' (hash trigger='nickname')) changeset=model.changeset + /tr + td= t 'models.plural.organization' + td= ui-multi-select options=possible_organizations labelProperty='name' selected=selected_organizations +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/user/index.emblem b/app/templates/user/index.emblem new file mode 100644 index 0000000..a7e8c6a --- /dev/null +++ b/app/templates/user/index.emblem @@ -0,0 +1,21 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.user' + +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + /= push-action type='edit' action='editUsersContext' + = push-action type='new' action='newUser' description='Add user' + = t 'models.plural.user' + table.ui.unstackable.table + thead + tr + th= t 'attributes.user.nickname' + th.actions + tbody + /each model as |user| + each (sort-by 'nickname' model) as |user| + tr + td= link-to user.nickname 'user.show' user + td.actions= push-action type='edit' action='editUser' model=user diff --git a/app/templates/user/profile.emblem b/app/templates/user/profile.emblem new file mode 100644 index 0000000..6ba4d47 --- /dev/null +++ b/app/templates/user/profile.emblem @@ -0,0 +1,17 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + span= model.nickname + breadcrumb.item active=true + span= t 'user.profile' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.user' + span.resource-name= model.nickname + table.ui.definition.table + tr + td= t 'models.nickname' + td= model.nickname + tr + td= t 'models.email' + td= model.email diff --git a/app/templates/user/show.emblem b/app/templates/user/show.emblem new file mode 100644 index 0000000..e8f207f --- /dev/null +++ b/app/templates/user/show.emblem @@ -0,0 +1,84 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'user.index': span= t 'models.plural.user' + breadcrumb.item active=true + span= t 'models.user' + span : + = model.nickname +.ui.container + .ui.header + .ui.right.floated.icon.buttons + = push-action type='edit' action='editUser' model=model + h2 + span.resource-descriptor= t 'models.user' + span.resource-name= model.nickname + table.ui.definition.table + tr + td= t 'attributes.user.nickname' + td= model.nickname + tr + td= t 'attributes.user.email' + td= model.email + + if (can 'manage' 'model' 'user') + .ui.two.column.grid + .column + .ui.top.attached.header.block + .content PANDA + .ui.right.floated.buttons + .ui.icon.button class="{{if (contains 'ban-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'ban'" + i.ban.icon + .or + .ui.button class="{{if (contains 'read-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'read'" + | read + .or + .ui.button class="{{if (contains 'manage-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'manage'" + | manage + unless (contains 'ban-app-panda' active_authorizations) + table.ui.bottom.attached.compact.definition.table + each panda_authorizations as |authorization| + tr + td + = authorization.type + ' + = authorization.target + td + .ui.buttons + .ui.icon.button class="{{if (contains (concat 'ban-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization 'ban'" + i.ban.icon + each authorization.permissions as |permission| + .or + .ui.button class="{{if (contains (concat permission '-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization permission" + = permission + each adapters as |adapter| + .column + .ui.top.attached.header.block + = adapter.title + .ui.right.floated.buttons + .ui.icon.button class="{{if (contains (concat 'ban-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'ban'" + i.ban.icon + .or + .ui.button class="{{if (contains (concat 'read-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'read'" + | read + .or + .ui.button class="{{if (contains (concat 'manage-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'manage'" + | manage + unless (contains (concat 'ban-adapter-' adapter.id) active_authorizations) + table.ui.bottom.attached.compact.definition.table + each adapter.authorizations as |authorization| + tr + td + = authorization.type + ' + = authorization.target + td + .ui.buttons + .ui.icon.button class="{{if (contains (concat 'ban-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization 'ban'" + i.ban.icon + each authorization.permissions as |permission| + .or + .ui.button class="{{if (contains (concat permission '-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization permission" + = permission + /= concat permission '-' authorization.type '-' authorization.target + .ui.segment + button.ui.button{action 'save_permissions'} Save permissions diff --git a/app/templates/workflow-action-definition/-authorize-users.emblem b/app/templates/workflow-action-definition/-authorize-users.emblem new file mode 100644 index 0000000..6467324 --- /dev/null +++ b/app/templates/workflow-action-definition/-authorize-users.emblem @@ -0,0 +1 @@ +workflow-action-definition/modal-header model=model diff --git a/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem b/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem new file mode 100644 index 0000000..6ef811a --- /dev/null +++ b/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem @@ -0,0 +1,244 @@ +workflow-action-definition/modal-header model=model +table.ui.compact.definition.table + tr + td= t 'attributes.identifier' + td= model.changeset.identifier + tr + td= t 'attributes.workflow_action_definition.title' + td= validated-input attribute='title' changeset=model.changeset enter=(action 'saveRecord' (hash trigger='title-action')) + tr + td= t 'attributes.workflow_action_definition.value_type' + td + = ui-dropdown class='selection' selected=model.changeset.value_type onChange=(action (mut model.changeset.value_type)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='attributes.workflow_action_definition.value_type' + i.dropdown.icon + .menu + each value_type_options as |value_type| + .item data-value=value_type + = value-type-display type=value_type + = t value_type scope='workflow_action_definition.value_type' + if (eq model.changeset.value_type 'select') + tr + td= t 'attributes.workflow_action_definition.value_type_select_options' + td + = ui-dropdown class='multiple fluid search selection' selected=model.changeset.value_type_select_options onChange=(action (mut model.changeset.value_type_select_options)) allowAdditions=true + .default.text options + i.dropdown.icon + .menu + each model.changeset.value_type_select_options as |option| + .item= option + tr + td= t 'attributes.workflow_action_definition.extra_info' + td= textarea value=model.extra_info + tr + td= t 'models.organization' + td + = ui-dropdown class='search selection' selected=model.changeset.organization onChange=(action 'selectOrganization') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.organization' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each possible_organizations as |organization| + .item data-value='{{map-value mapper organization}}' + if organization.logo + img src=organization.logo_url.small + = organization.name + tr + td= t 'attributes.workflow_action_definition.completes_group' + td= ui-checkbox checked=model.changeset.completes_group onChange=(action (mut model.changeset.completes_group)) + tr + td= t 'attributes.workflow_action_definition.confirms_workflow_action_definition' + td= model.confirms_workflow_action_definition.title + tr + td= t 'attributes.workflow_action_definition.confirmed_by_workflow_action_definition' + td= model.confirmed_by_workflow_action_definition.title + if model.project_scenarios.length + tr + td= t 'models.plural.project_scenario' + td= join ', ' (map-by 'name' model.project_scenarios) + +.ui.dividing.header + .ui.right.floated.buttons + .ui.button class=offset_type_none:active click="set_offset_type_none" + span= t 'workflow_action_definition.edit_workflow_action_definition.no_offset' + .or + .ui.button class=offset_type_attached:active click='set_offset_type_attached' + = t 'workflow_action_definition.edit_workflow_action_definition.attached_offset' + .or + .ui.button class=offset_type_configured:active click='set_offset_type_configured' + = t 'workflow_action_definition.edit_workflow_action_definition.configured_offset' + .content + if offset_type_configured + = t 'models.plural.workflow_action_offset' + span   + = push-action type='new' action='add_workflow_action_offset' + else + = t 'models.workflow_action_offset' +if offset_type_attached + span create connection to other action + table.ui.compact.definition.table + tr + td= t 'models.workflow_action_definition' + td + = ui-dropdown class='search selection' selected=attached_to_workflow_action_workflow_group onChange=(action 'set_attached_to_workflow_action_workflow_group') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=attached_to_workflow_action onChange=(action 'set_attached_to_workflow_action') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each possible_attached_to_workflow_action_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + if attached_to_connection_warning + .ui.negative.message= attached_to_connection_warning + tr + td= t 'attributes.workflow_action_connection.conditional_base_value' + td + if (eq attached_to_workflow_action.value_type 'boolean') + = ui-dropdown selected=attached_to_connection_conditional_base_value onChange=(action (mut attached_to_connection_conditional_base_value)) + span.default.text -- + i.dropdown.icon + .menu + .item data-value='' + i.square.outline.icon + .item data-value='true' + i.checkmark.box.icon + else if (eq attached_to_workflow_action.value_type 'select') + = ui-dropdown selected=attached_to_connection_conditional_base_value onChange=(action (mut attached_to_connection_conditional_base_value)) + span.default.text - + i.dropdown.icon + .menu + .item data-value='' + span - + each attached_to_workflow_action.value_type_select_options as |option| + .item= option + else + = input value=attached_to_connection_conditional_base_value + +if offset_type_configured + if new_workflow_action_offset_warning + .ui.visible.negative.message= new_workflow_action_offset_warning + each non_deleted_working_workflow_action_offsets as |offset| + table.ui.compact.definition.table + tr + td= t 'attributes.workflow_action_offset.base_workflow_action_definition' + td + unless offset.x_based + = push-action type='destroy' action=(action 'destroyWorkflowActionOffset' offset) class='pull-right' + = ui-dropdown class='search selection' selected=offset.selector_workflow_group onChange=(action 'set_selector_workflow_group' offset) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=offset.base_workflow_action_definition onChange=(action 'set_base_workflow_action_definition' offset) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each offset.filtered_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + ui-checkbox class='toggle' label='X-based' checked=offset.x_based onChange=(action 'toggle_offset_x_based' offset) + if offset.base_workflow_action_definition_warning + .ui.negative.message= offset.base_workflow_action_definition_warning + tr + td= t 'attributes.workflow_action_definition.offset_value' + td= select-duration value=offset.offset_value unit=offset.offset_unit week_days=offset.offset_business_days + if model.project.instance_scope_batch + tr + td= t 'attributes.workflow_action_definition.batch_element_threshold' + td= select-duration value=offset.batch_element_threshold_value unit=offset.batch_element_threshold_unit week_days=offset.batch_element_threshold_business_days threshold=true + if model.project.project_scenarios.length + tr + td= t 'models.project_scenario' + td + = ui-dropdown class='search selection' selected=offset.project_scenario onChange=(action (mut offset.project_scenario)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each model.project.project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + else + .ui.visible.warning.message= t 'workflow_action_definition.edit_workflow_action_definition.no_workflow_action_offsets_present' + +unless offset_type_none + .ui.divider +h3.ui.dividing.header: .content + = t 'workflow_action_definition.pointing_connections_title' + ' + = push-action type='new' action='add_workflow_action_connection' +each non_deleted_working_pointing_workflow_action_connections as |pointing_connection| + unless (eq pointing_connection.connection_type 'attached') + .ui.segment + = pointing_connection.workflow_action_definition.title + = push-action type='destroy' action=(action 'destroyWorkflowActionConnection' pointing_connection) class='pull-right' + = ui-dropdown class='search selection' selected=pointing_connection.selector_workflow_group onChange=(action 'set_connection_selector_workflow_group' pointing_connection) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=pointing_connection.workflow_action_definition onChange=(action 'set_connection_workflow_action_definition' pointing_connection) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each pointing_connection.filtered_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + ' + = ui-dropdown selected=pointing_connection.connection_type onChange=(action (mut pointing_connection.connection_type)) + .default.text connection type + i.dropdown.icon + .menu + each connection_types as |connection_type| + .item data-value=connection_type + = t connection_type scope='workflow_action_definition.connection_type' + if pointing_connection.workflow_action_definition_warning + .ui.negative.message= pointing_connection.workflow_action_definition_warning +/ CONSTRAINTS +/.ui.dividing.header: .content= t 'models.plural.workflow_action_constraint' +/if new_workflow_action_constraint_warning + .ui.visible.warning.message= new_workflow_action_constraint_warning +/.ui.grid + .three.wide.column + x-select value=selectedConstraintType on-change=(action (mut selectedConstraintType)) as |xs| + each constraint_types as |constraint_type| + xs.option value=constraint_type + = constraint_type + .six.wide.column + x-select value=lastSelectedWorkflowGroup on-change=(action 'workflow_group_change') as |xs| + each model.project.sorted_workflow_groups as |workflow_group| + xs.option value=workflow_group + = workflow_group.name + .seven.wide.column + x-select value=lastSelectedWorkflowActionDefinition on-change=(action 'newWorkflowActionConstraint') as |xs| + option -- + each lastSelectedWorkflowGroup.sorted_workflow_action_definitions as |workflow_action_definition| + unless (eq workflow_action_definition model) + xs.option value=workflow_action_definition + = workflow_action_definition.title +/.ui.grid + each (reject-by 'isDeleted' working_workflow_action_constraints) as |constraint| + .five.wide.column= constraint.constraint_workflow_action_definition.workflow_group.name + .three.wide.column: .ui.right.pointing.label class=constraint.restrict:red:grey + = constraint.type + .six.wide.column= constraint.constraint_workflow_action_definition.title + .two.wide.column= push-action type='destroy' action=(action 'destroyWorkflowActionConstraint' constraint) + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{ action 'rollback_and_close'}= t 'general.rollback' + push-action type='position' action='positionInProject' + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{ action 'destroyRecord'}= t 'general.destroy.button' diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..5e425e0 --- /dev/null +++ b/bower.json @@ -0,0 +1,7 @@ +{ + "name": "ember-panda", + "dependencies": { + "semantic-ui": "^2.2.13", + "semantic-ui-calendar": "^0.0.8" + } +} diff --git a/config/ember-try.js b/config/ember-try.js new file mode 100644 index 0000000..21f6079 --- /dev/null +++ b/config/ember-try.js @@ -0,0 +1,83 @@ +/* eslint-env node */ +module.exports = { + scenarios: [ + { + name: 'ember-lts-2.8', + bower: { + dependencies: { + 'ember': 'components/ember#lts-2-8' + }, + resolutions: { + 'ember': 'lts-2-8' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-lts-2.12', + npm: { + devDependencies: { + 'ember-source': '~2.12.0' + } + } + }, + { + name: 'ember-release', + bower: { + dependencies: { + 'ember': 'components/ember#release' + }, + resolutions: { + 'ember': 'release' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-beta', + bower: { + dependencies: { + 'ember': 'components/ember#beta' + }, + resolutions: { + 'ember': 'beta' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-canary', + bower: { + dependencies: { + 'ember': 'components/ember#canary' + }, + resolutions: { + 'ember': 'canary' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-default', + npm: { + devDependencies: {} + } + } + ] +}; diff --git a/config/environment.js b/config/environment.js new file mode 100644 index 0000000..012a412 --- /dev/null +++ b/config/environment.js @@ -0,0 +1,6 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(/* environment, appConfig */) { + return { }; +}; diff --git a/ember-cli-build.js b/ember-cli-build.js new file mode 100644 index 0000000..156e23f --- /dev/null +++ b/ember-cli-build.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +'use strict'; + +const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); + +module.exports = function(defaults) { + let app = new EmberAddon(defaults, { + // Add options here + }); + + /* + This build file specifies the options for the dummy test app of this + addon, located in `/tests/dummy` + This build file does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's build file + */ + + return app.toTree(); +}; diff --git a/esa-coffee-workaround.sh b/esa-coffee-workaround.sh new file mode 100644 index 0000000..0a23b26 --- /dev/null +++ b/esa-coffee-workaround.sh @@ -0,0 +1,6 @@ +# ember-simple-auth's routes/application.js conflicts with the routes/application.js +# that results from compilation of our routes/application.coffee. +# ESA's application route doesn't seem to be necessary. +# https://github.com/simplabs/ember-simple-auth/issues/851 +echo "deleting ember-simple-auth's application route to workaround incompatibility with coffeescript..." +rm -f node_modules/ember-simple-auth/app/routes/application.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..509588f --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +/* eslint-env node */ +'use strict'; + +module.exports = { + name: 'ember-panda' +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..52fe6d5 --- /dev/null +++ b/package.json @@ -0,0 +1,81 @@ +{ + "name": "ember-panda", + "version": "0.0.0", + "description": "The default blueprint for ember-cli addons.", + "keywords": [ + "ember-addon" + ], + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "repository": "", + "scripts": { + "build": "ember build", + "start": "ember server", + "test": "ember try:each", + "postinstall": "sh esa-coffee-workaround.sh" + }, + "dependencies": { + "ember-cli-babel": "^6.3.0", + "ember-cli-dunlop": "^0.0.0", + "ember-cli-htmlbars": "^2.0.3", + "ember-cli-sass": "^7.0.0" + }, + "devDependencies": { + "active-model-adapter": "^2.1.1", + "broccoli-asset-rev": "^2.4.5", + "ember-ajax": "^3.0.0", + "ember-browserify": "^1.2.0", + "ember-changeset-validations": "^1.2.8", + "ember-cli": "^2.16.2", + "ember-cli-coffeescript": "^1.16.1", + "ember-cli-d3": "^1.1.7", + "ember-cli-dependency-checker": "^2.0.0", + "ember-cli-emblem": "^0.9.0", + "ember-cli-eslint": "^4.2.1", + "ember-cli-htmlbars-inline-precompile": "^1.0.0", + "ember-cli-inject-live-reload": "^1.4.1", + "ember-cli-moment-shim": "^3.4.0", + "ember-cli-nouislider": "^0.13.0", + "ember-cli-pagination": "^3.1.0", + "ember-cli-qunit": "^4.0.0", + "ember-cli-shims": "^1.1.0", + "ember-cli-showdown": "^4.1.0", + "ember-cli-sri": "^2.1.0", + "ember-cli-tooltipster": "^0.8.2", + "ember-cli-uglify": "^1.2.0", + "ember-composable-helpers": "^2.0.3", + "ember-concurrency": "^0.8.10", + "ember-cpm": "^3.0.0", + "ember-data": "~2.16.3", + "ember-export-application-global": "^2.0.0", + "ember-file-upload": "^2.1.1", + "ember-get-config": "^0.2.2", + "ember-group-by": "0.0.3", + "ember-load-initializers": "^1.0.0", + "ember-math-helpers": "^2.2.3", + "ember-one-way-controls": "^2.0.1", + "ember-resolver": "^4.0.0", + "ember-route-action-helper": "^2.0.6", + "ember-semantic-ui-calendar": "^0.2.0", + "ember-simple-auth": "^1.4.0", + "ember-sortable": "^1.9.4", + "ember-source": "~2.16.2", + "ember-truth-helpers": "^1.3.0", + "emberx-select": "^3.1.0", + "json-formatter-js": "^2.2.0", + "loader.js": "^4.2.3", + "semantic-ui-ember": "^2.0.1", + "tooltipster": "^4.2.5", + "viz.js": "^1.8.0" + }, + "engines": { + "node": "^4.5 || 6.* || >= 7.*" + }, + "ember-addon": { + "configPath": "tests/dummy/config" + } +} diff --git a/public/assets/icons/svg/logical-conjunction.svg b/public/assets/icons/svg/logical-conjunction.svg new file mode 100644 index 0000000..8c69f92 --- /dev/null +++ b/public/assets/icons/svg/logical-conjunction.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-conjunction_backup.svg b/public/assets/icons/svg/logical-conjunction_backup.svg new file mode 100644 index 0000000..e50fd3e --- /dev/null +++ b/public/assets/icons/svg/logical-conjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-disjunction.svg b/public/assets/icons/svg/logical-disjunction.svg new file mode 100644 index 0000000..433e0f9 --- /dev/null +++ b/public/assets/icons/svg/logical-disjunction.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/public/assets/icons/svg/logical-disjunction_backup.svg b/public/assets/icons/svg/logical-disjunction_backup.svg new file mode 100644 index 0000000..73cea24 --- /dev/null +++ b/public/assets/icons/svg/logical-disjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-drawing-board.svg b/public/assets/icons/svg/logical-drawing-board.svg new file mode 100644 index 0000000..28fe65f --- /dev/null +++ b/public/assets/icons/svg/logical-drawing-board.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/public/assets/under-construction.svg b/public/assets/under-construction.svg new file mode 100644 index 0000000..c472e10 --- /dev/null +++ b/public/assets/under-construction.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + + diff --git a/testem.js b/testem.js new file mode 100644 index 0000000..a40a530 --- /dev/null +++ b/testem.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +module.exports = { + test_page: 'tests/index.html?hidepassed', + disable_watching: true, + launch_in_ci: [ + 'Chrome' + ], + launch_in_dev: [ + 'Chrome' + ], + browser_args: { + Chrome: [ + '--disable-gpu', + '--headless', + '--remote-debugging-port=9222', + '--window-size=1440,900' + ] + } +}; diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js new file mode 100644 index 0000000..fbf2555 --- /dev/null +++ b/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + embertest: true + } +}; diff --git a/tests/dummy/app/adapters/application.coffee b/tests/dummy/app/adapters/application.coffee new file mode 100644 index 0000000..9114c07 --- /dev/null +++ b/tests/dummy/app/adapters/application.coffee @@ -0,0 +1,9 @@ +import DunlopAdapter from 'ember-cli-dunlop/adapters/application' + +# Override the default adapter with the `DS.ActiveModelAdapter` which +Ember.$.ajaxPrefilter (options, oriOpt, jqXHR)-> + jqXHR.setRequestHeader 'client-id', window.client_id + +export default DunlopAdapter.extend + pathForType: (modelName) -> + @_super modelName.replace(/^panda[\/\.]/, '') diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js new file mode 100644 index 0000000..7e6263d --- /dev/null +++ b/tests/dummy/app/app.js @@ -0,0 +1,17 @@ +import Ember from 'ember'; +import Resolver from './resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from './config/environment'; +import './i18n/translations'; +import 'ember-panda/utils/moment-business-days'; + +const App = Ember.Application.extend({ + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + ready: function(){window.store = this.__container__.lookup('service:store')}, /* what we can do with testing */ + Resolver +}); + +loadInitializers(App, config.modulePrefix); + +export default App; diff --git a/tests/dummy/app/components/.gitkeep b/tests/dummy/app/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/controllers/.gitkeep b/tests/dummy/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/helpers/.gitkeep b/tests/dummy/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/i18n/translations.coffee b/tests/dummy/app/i18n/translations.coffee new file mode 100644 index 0000000..0815492 --- /dev/null +++ b/tests/dummy/app/i18n/translations.coffee @@ -0,0 +1,95 @@ +window.$translations = + en: + application_name: 'Panda' + total: 'Total' + user: + sign_out: 'Uitloggen' + dunlop: + module: + wba_mailbox: 'WBA Mailbox' + kbt_endpoint: 'KBT endpoint' + general: + save: 'Save' + rollback: 'Cancel' + destroy: + are_you_sure: 'Weet je dit zeker?' + button: 'Delete' + close: 'Close' + confirm: + cancel: 'Cancel' + confirm: 'Yes' + modals: + edit_project: + title: "Edit ${models.project} %{name}" + edit_organization: + title: "Edit ${models.organization} %{name}" + edit_tracker_subject: + title: "Edit ${models.tracker_subject} %{name}" + edit_tracker_group: + title: "Edit ${models.tracker_group} %{name}" + + confirm: 'Ja' + action: + new_model: "New %{model}" + select_model: 'Select %{model}' + add_model: 'Add %{model}' + + date: + today: 'Today' + this_week: 'This week' + this_month: 'This month' + unit: + second: 'second' + minute: 'minute' + businessday: 'business day' + day: 'day' + week: 'week' + year: 'year' + plural: + second: 'seconds' + minute: 'minutes' + businessday: 'business days' + day: 'days' + week: 'weeks' + year: 'years' + week_day: + monday: 'Maandag' + tuesday: 'Dinsdag' + wednesday: 'Woensdag' + thursday: 'Donderdag' + friday: 'Vrijdag' + saturday: 'Zaterdag' + sunday: 'Zondag' + +import model_translations from './translations/models' +Ember.assign window.$translations.en, model_translations + +import attribute_translations from './translations/attributes' +Ember.assign window.$translations.en, attribute_translations + +import manual_translations from './translations/manual' +Ember.assign window.$translations.en, manual_translations + +import organization_translations from './translations/organization' +Ember.assign window.$translations.en, organization_translations + +import project_translations from './translations/project' +Ember.assign window.$translations.en, project_translations + +import project_instance_translations from './translations/project-instance' +Ember.assign window.$translations.en, project_instance_translations + +import project_instance_action_translations from './translations/project-instance-action' +Ember.assign window.$translations.en, project_instance_action_translations + +import user_translations from './translations/user' +Ember.assign window.$translations.en, user_translations + +import action_list_translations from './translations/action-list' +Ember.assign window.$translations.en, action_list_translations + +import workflow_action_definition_translations from './translations/workflow-action-definition' +Ember.assign window.$translations.en, workflow_action_definition_translations + +import workflow_group_translations from './translations/workflow-group' +Ember.assign window.$translations.en, workflow_group_translations diff --git a/tests/dummy/app/i18n/translations/action-list.coffee b/tests/dummy/app/i18n/translations/action-list.coffee new file mode 100644 index 0000000..65b8015 --- /dev/null +++ b/tests/dummy/app/i18n/translations/action-list.coffee @@ -0,0 +1,5 @@ +translations = action_list: + title: 'Action list' + start_inquiry: 'Start ${attributes.state.inquiry}' + add_note: 'Add note' +export default translations diff --git a/tests/dummy/app/i18n/translations/attributes.coffee b/tests/dummy/app/i18n/translations/attributes.coffee new file mode 100644 index 0000000..a69e6a6 --- /dev/null +++ b/tests/dummy/app/i18n/translations/attributes.coffee @@ -0,0 +1,74 @@ +translations = attributes: + identifier: 'Identity' + state: + pending: 'pending' + uncategorized: 'uncategorized' + unplanned: 'unplanned' + planned: 'planned' + overdue: 'overdue' + rejected: 'rejected' + completed: 'completed' + user: + email: 'E-mail' + nickname: 'Nickname' + organization: + name: 'Name' + logo: 'Logo' + project: + name: 'Name' + start_date: 'Start date' + end_date: 'End date' + percent_done: 'Progress' + expected_number_of_workflow_instances: "# instances" + batch_size_lower_value: 'Batch size lower' + batch_size_upper_value: 'Batch size upper' + reference_date_day_of_week: 'Migratie datum dag in de week' + reference_date: 'Migratie datum' + project_instance: + reference_date: 'Date (X)' + state: 'State' + client_identifier: 'Instance ID' + batch_size: '# Batch' + project_instance_group: + state: 'State' + project_instance_action: + state: 'State' + target_date: 'Action Date' + value: 'Value' + notes: 'Info' + completed_at: 'Completed' + overdue_at: 'Overdue' + inquiry_at: 'Inquiry' + project_scenario: + name: 'Name' + tracker_group: + name: 'Name' + tracker_subject: + name: 'Name' + edit_from_workflow_group: "Zet in ${models.workflow_group}?" + workflow_action_definition: + identifier: '${attributes.identifier}' + title: 'Label' + value_type: 'Type' + offset_value: 'Offset' + extra_info: 'Description' + #action_offset_base: 'Offset base' + batch_element_threshold: 'Batch element threshold' + completes_group: 'Completes group?' + confirms_workflow_action_definition: 'Confirms ${models.workflow_action_definition}' + confirmed_by_workflow_action_definition: 'Confirmed by ${models.workflow_action_definition}' + value_type_select_options: 'Select options' + workflow_group: + identifier: '${attributes.identifier}' + name: 'Name' + offset_value: 'Offset' + offset_base: 'Offset base' + duration_value: 'Duration' + batch_element_threshold_value: 'Batch elements period threshold' + offset_type: 'Planning' + #batch_element_threshold_unit: 'Batch element duration unit' + workflow_action_offset: + base_workflow_action_definition: 'Base ${models.workflow_action_definition}' + workflow_action_connection: + conditional_base_value: 'Visible if ${models.workflow_action_definition} is' +export default translations diff --git a/tests/dummy/app/i18n/translations/manual.coffee b/tests/dummy/app/i18n/translations/manual.coffee new file mode 100644 index 0000000..8a50fed --- /dev/null +++ b/tests/dummy/app/i18n/translations/manual.coffee @@ -0,0 +1,24 @@ +translations = manual: + title: 'Panda manual' + introduction1: " +

Focus points:

+
    +
  • Project scenarios
  • +
  • Action bound group lead times
  • +
  • Project progress prognosis
  • +
  • Instance specific action planning maintenance
  • +
  • Multi path action specifications
  • +
  • Manual addition of basic concept explanations
  • +
+

Planning steps

+
    +
  • Conceptual planning
  • +
  • Actualized instance set planning
  • +
  • Operational interpretation and possible operator insight modifiactions
  • +
  • Operational result of actions
  • +
  • Analysis of operation and comparison with actualized instance set planning
  • +
+" +import project_translations from './manual/project' +Ember.assign translations.manual, project_translations +export default translations diff --git a/tests/dummy/app/i18n/translations/manual/project.coffee b/tests/dummy/app/i18n/translations/manual/project.coffee new file mode 100644 index 0000000..0a43602 --- /dev/null +++ b/tests/dummy/app/i18n/translations/manual/project.coffee @@ -0,0 +1,13 @@ +translations = project: + dunlop: + workflow_group: + text: " +%{models} are groupings of ${models.plural.workflow_action_definition}. +" + duration_value: + header: 'Execution lead time, offset and order' + workflow_action_definition: + action_confirmation: + header: '${models.workflow_action_definition} confirmation' + text: '' +export default translations diff --git a/tests/dummy/app/i18n/translations/models.coffee b/tests/dummy/app/i18n/translations/models.coffee new file mode 100644 index 0000000..a123e82 --- /dev/null +++ b/tests/dummy/app/i18n/translations/models.coffee @@ -0,0 +1,39 @@ +translations = models: + event: 'Event' + organization: 'Organisatie' + project: 'Project' + project_scenario: 'Scenario' + project_instance: 'Project Instance' + project_instance_action: 'Instance Action' + project_instance_group: 'Instance Group' + workflow_action_definition: 'Action' + workflow_group: 'Workflow Group' + workflow_action_constraint: 'Constraint' + workflow_action_offset: 'Action offset' + workflow_group_constraint: 'Constraint' + tracker_subject: "Tracker" + tracker_group: "Tracker Group" + tracker_item: "Tracker Item" + user: "User" + workflow_instance_type: "Instance Type" + source_file: "Bronbestand" + plural: + event: 'Events' + organization: 'Organisaties' + project: 'Projecten' + project_scenario: 'Scenarios' + project_instance: 'Project Instances' + project_instance_action: 'Instance Actions' + project_instance_group: 'Instance Groups' + workflow_action_definition: 'Actions' + workflow_group: 'Workflow Groups' + workflow_action_constraint: 'Constraints' + workflow_action_offset: 'Action offsets' + workflow_group_constraint: 'Constraints' + tracker_subject: "Trackers" + tracker_group: "Tracker Groups" + tracker_item: "Tracker Items" + user: "Users" + workflow_instance_type: "Instance Types" + source_file: "Bronbestanden" +export default translations diff --git a/tests/dummy/app/i18n/translations/organization.coffee b/tests/dummy/app/i18n/translations/organization.coffee new file mode 100644 index 0000000..54aa4d8 --- /dev/null +++ b/tests/dummy/app/i18n/translations/organization.coffee @@ -0,0 +1,8 @@ +translations = organization: + project_actions: + title: 'Project actions' + select_users: + placeholder: 'Select ${models.plural.user} (%{count})' + edit_organization: + title: 'Edit ${models.organization} %{name}' +export default translations diff --git a/tests/dummy/app/i18n/translations/project-instance-action.coffee b/tests/dummy/app/i18n/translations/project-instance-action.coffee new file mode 100644 index 0000000..f4e6ed2 --- /dev/null +++ b/tests/dummy/app/i18n/translations/project-instance-action.coffee @@ -0,0 +1,5 @@ +translations = project_instance_action: + revoke: 'Revoke!' + done: 'Done!' + has_uncompleted_constraints_message: 'Not al required preceding actions are completed' +export default translations diff --git a/tests/dummy/app/i18n/translations/project-instance.coffee b/tests/dummy/app/i18n/translations/project-instance.coffee new file mode 100644 index 0000000..4942c30 --- /dev/null +++ b/tests/dummy/app/i18n/translations/project-instance.coffee @@ -0,0 +1,6 @@ +translations = project_instance: + show: + grid: 'Grid' + list: 'List' + graphs: 'Graphs' +export default translations diff --git a/tests/dummy/app/i18n/translations/project.coffee b/tests/dummy/app/i18n/translations/project.coffee new file mode 100644 index 0000000..e51abcd --- /dev/null +++ b/tests/dummy/app/i18n/translations/project.coffee @@ -0,0 +1,36 @@ +translations = project: + dunlop: + title: 'Organize ${models.project}' + analysis: + title: '${models.project} analysis' + dunlop_generator: + title: 'Gererate operational application' + add_progress: + title: 'Add progress files' + diagram_title: '%{model} diagram' + select_reference_date_day_of_week: 'Select ${attributes.project.reference_date_day_of_week}' + edit_projects_context: + title: 'Manage project types' + message: 'Manage the different project type options' + edit_workflow_group: + title: 'Edit ${models.workflow_group}: %{name}' + edit_project: + title: 'Edit ${models.project} %{name}' + position_workflow_action_definition_in_project: + title: "Position ${models.workflow_action_definition} in ${models.project}" + contract: + title: '${models.project} %{name} contract' + new_project_from_contract: + title: 'New ${models.project} from contract' + edit_project_scenario: + title: 'Edit ${models.project_scenario} %{name}' + project_scenarios_show_grid: + title: 'Show ${models.plural.project_scenario} activation grid' + load_project_instances: + title: 'Load ${models.project} instances' + edit_instance_action: + title: 'Edit action: %{title}' + show: + dunlop_project_info: + title: "${models.project} %{name} info" +export default translations diff --git a/tests/dummy/app/i18n/translations/user.coffee b/tests/dummy/app/i18n/translations/user.coffee new file mode 100644 index 0000000..85ccda3 --- /dev/null +++ b/tests/dummy/app/i18n/translations/user.coffee @@ -0,0 +1,8 @@ +translations = user: + profile: 'Profile' + sign_out: 'Uitloggen' + new_user: + title: 'New ${models.user}' + edit_user: + title: 'Edit ${models.user} %{nickname}' +export default translations diff --git a/tests/dummy/app/i18n/translations/workflow-action-definition.coffee b/tests/dummy/app/i18n/translations/workflow-action-definition.coffee new file mode 100644 index 0000000..7825697 --- /dev/null +++ b/tests/dummy/app/i18n/translations/workflow-action-definition.coffee @@ -0,0 +1,37 @@ +translations = workflow_action_definition: + authorize_users: + title: 'Authorize ${models.plural.user}' + value_type: + string: 'String' + select: 'Select' + text: 'Text' + timestamp: 'Timestamp' + boolean: 'Boolean' + date: 'Date' + number: 'Number' + connection_type: + info: 'INFO' + constraint: 'CONSTRAINT' + pointing_connections_title: 'Pointing connections' + edit_workflow_action_definition: + title: 'Edit ${models.workflow_action_definition}: %{title}' + #no_workflow_action_definition_for_offset_selected_alert: 'Please select ${models.workflow_action_definition} first' + #no_workflow_action_definition_for_constraint_selected_alert: 'Please select ${models.workflow_action_definition} first' + no_offset: 'No offset' + attached_offset: 'Attached' + configured_offset: 'Configured' + no_workflow_action_offsets_present: 'No ${models.plural.workflow_action_offset} added' + #no_action_offset_base_given_alert: 'No ${models.workflow_action_definition} selected' + #chosen_but_non_added_offset_workflow_action_definition: 'You selected a ${models.workflow_action_definition} but did not add it as an offset. Clear the selection or add the ${models.workflow_action_definition} to proceed' + offset_type_configured_but_no_offsets: 'You chose offset type ${workflow_action_definition.edit_workflow_action_definition.configured_offset} but did not add any ${models.plural.workflow_action_offset}' + offset_type_attached_but_no_connection: 'You chose offset type ${workflow_action_definition.edit_workflow_action_definition.attached_offset} but there is no attached ${models.workflow_action_definition}' + workflow_action_definition_already_used: '${models.workflow_action_definition} %{title} can be used only once' + base_workflow_action_definition_not_present: '${attributes.workflow_action_offset.base_workflow_action_definition} not selected' + workflow_action_definition_not_present: '${models.workflow_action_definition} not present' + cannot_set_offset_value_due_to_workflow_action_offset_presence: '${attributes.workflow_action_definition.offset_value} from ${attributes.project_instance.reference_date} cannot be set when there are ${models.plural.workflow_action_offset}' + cannot_add_workflow_action_offsets_due_to_offset_value_presence: 'Cannot add ${models.plural.workflow_action_offset} due to ${attributes.workflow_action_definition.offset_value} presence' + offset_graph: + show_attached: "Show attached" + show_pointing_infos: "Show info ${models.plural.workflow_action_definition}" + show_pointing_constraints: "Show constraint ${models.plural.workflow_action_definition}" +export default translations diff --git a/tests/dummy/app/i18n/translations/workflow-group.coffee b/tests/dummy/app/i18n/translations/workflow-group.coffee new file mode 100644 index 0000000..25488c0 --- /dev/null +++ b/tests/dummy/app/i18n/translations/workflow-group.coffee @@ -0,0 +1,21 @@ +translations = workflow_group: + offset_type: + none: + label: 'Geen' + reference_date: + label: '${attributes.project_instance.reference_date}' + offset_value_label: 'start offset' + connected_finish: + label: 'connected finish' + offset_value_label: '${models.workflow_group} finishes' + offset_base_label: 'before' + offset_base_prefix: 'start of -> ' + connected_start: + label: 'connected start' + offset_value_label: '${models.workflow_group} starts' + offset_base_label: 'after' + offset_base_prefix: 'finishing of -> ' + modals: + edit_workflow_group: + title: 'Edit ${models.workflow_group}: %{name}' +export default translations diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html new file mode 100644 index 0000000..61400b2 --- /dev/null +++ b/tests/dummy/app/index.html @@ -0,0 +1,25 @@ + + + + + + Dummy + + + + {{content-for "head"}} + + + + + {{content-for "head-footer"}} + + + {{content-for "body"}} + + + + + {{content-for "body-footer"}} + + diff --git a/tests/dummy/app/initializers/globals.coffee b/tests/dummy/app/initializers/globals.coffee new file mode 100644 index 0000000..60916d0 --- /dev/null +++ b/tests/dummy/app/initializers/globals.coffee @@ -0,0 +1,50 @@ +window.client_id ||= "client#{Math.round(Math.random()*1000000)}" +#$translations.en.attributes.state ||= {} +$translations.en.attributes.state ||= {} +Ember.assign $translations.en.attributes.state, + inquiry: 'Issue' +default_globals = + flash_message: '' + preload_resources_fulfilled: true + current_user: null + workflow_action_definition: + value_type_options: ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp'] + workflow_action_offset: + offset_unit_options: ['day'] + week_days: [ + {name: 'monday', index: 1} + {name: 'tuesday', index: 2} + {name: 'wednesday', index: 3} + {name: 'thursday', index: 4} + {name: 'friday', index: 5} + {name: 'saturday', index: 6} + {name: 'sunday', index: 0} + ] + project: + instance_scope_options: ['instance', 'batch'] + workflow_group: + batch_element_threshold_unit_options: ['businessday', 'day', 'week'] + project_instance_action: + states: ['pending', 'planned', 'overdue', 'inquiry', 'rejected', 'completed'] + final_states: ['inquiry', 'rejected', 'completed'] + + time_formatting: 'LLL' + date_formatting: 'LL' + +export globals = Ember.Object.create default_globals, window.app_globals + +initialize = (application) -> + application.register 'service:globals', globals, instantiate: false #, singleton: true + application.inject 'controller', 'globals', 'service:globals' + application.inject 'component', 'globals', 'service:globals' + application.inject 'component', 'router', 'router:main' + application.inject 'component', 'store', 'service:store' + application.inject 'view', 'globals', 'service:globals' + application.inject 'router', 'globals', 'service:globals' + +GlobalsInitializer = + name: 'globals' + initialize: initialize + +export {initialize} +export default GlobalsInitializer diff --git a/tests/dummy/app/models/.gitkeep b/tests/dummy/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/models/user.coffee b/tests/dummy/app/models/user.coffee new file mode 100644 index 0000000..7e4da4f --- /dev/null +++ b/tests/dummy/app/models/user.coffee @@ -0,0 +1,8 @@ +import DS from 'ember-data' +import dunlop_user from 'ember-cli-dunlop/models/dunlop-user' + +export default dunlop_user.extend + organizations: DS.hasMany('panda/organization', serialize: 'ids', inverse: 'users') + knock_knock: -> 'I\'m here!' + + diff --git a/tests/dummy/app/resolver.js b/tests/dummy/app/resolver.js new file mode 100644 index 0000000..2fb563d --- /dev/null +++ b/tests/dummy/app/resolver.js @@ -0,0 +1,3 @@ +import Resolver from 'ember-resolver'; + +export default Resolver; diff --git a/tests/dummy/app/router.coffee b/tests/dummy/app/router.coffee new file mode 100644 index 0000000..7689053 --- /dev/null +++ b/tests/dummy/app/router.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +import config from './config/environment' +import pandaRouteSetup from 'ember-panda/utils/panda-route-setup' + +Router = Ember.Router.extend + location: config.locationType + rootURL: config.rootURL + +Router.map -> + pandaRouteSetup(@) + +export default Router diff --git a/tests/dummy/app/routes/.gitkeep b/tests/dummy/app/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/routes/application.coffee b/tests/dummy/app/routes/application.coffee new file mode 100644 index 0000000..f7f2144 --- /dev/null +++ b/tests/dummy/app/routes/application.coffee @@ -0,0 +1,56 @@ +import Ember from 'ember' +import DunlopApplicationRoute from 'ember-cli-dunlop/mixins/application-route' + +export default Ember.Route.extend DunlopApplicationRoute, + # this method gets fired after a successful authentication or successful fetch of current user through + # HTTP session or ember-simple-auth session + current_user_loaded: (user) -> + @_super arguments... + @preloadResources() + + #setupController: (controller)-> + # host = @store.adapterFor('application').host || '' + # event_host = "#{host}/faye" + # faye = new Faye.Client(event_host) + # faye.subscribe '/activity', (data)=> + # return if data.client_id is window.client_id # prevent duplicate records with same id, still an issue in ember 2.9.0/1 + # console.log data + # switch data.event + # when 'payload' + # data.payload = JSON.parse(data.payload) if typeof(data.payload) is 'string' + # @store.pushPayload(data.payload) + # when 'destroy' + # if record = @store.peekRecord(data.model, data.id) + # record.rollbackAttributes() + # record.unloadRecord() + + preloadResources: -> + @store.findAll 'panda/workflow-instance-type' + @store.findAll 'panda/organization' + @store.findAll 'user' + @store.findAll 'panda/tracker-subject' + @set 'router.globals.adapters', @store.findAll 'panda/adapter' + #Ember.RSVP.all([ + # @store.findAll 'workflow-instance-type' + # @store.findAll 'organization' + # @store.findAll 'user' + # @store.findAll 'tracker-subject' + #]).then => + # @set 'router.globals.preload_resources_fulfilled', true + + actions: + debug: -> debugger + dateSort: (attribute, a, b) -> + date_a = Ember.get(a, attribute) + date_b = Ember.get(b, attribute) + return 0 if date_a is date_b + return 1 unless date_b + return -1 unless date_a + date_a_string = date_a.toISOString() + date_b_string = date_b.toISOString() + if date_a_string > date_b_string + return 1 + else if date_a_string < date_b_string + return -1 + 0 + diff --git a/tests/dummy/app/styles/app.scss b/tests/dummy/app/styles/app.scss new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/application.emblem b/tests/dummy/app/templates/application.emblem new file mode 100644 index 0000000..7c21e96 --- /dev/null +++ b/tests/dummy/app/templates/application.emblem @@ -0,0 +1,28 @@ += flash-message += partial 'global/top-menu' +.ui.main + /bread-crumbs linkable=false + /= bread-crumbs tagName='ul' outputStyle='foundation' linkable=false as |component rrr| + = bread-crumb route=rrr breadCrumbs=component + span= rrr.title + = outlet 'error-handling' + if globals.flash.alert + .ui.negative.floating.message + i.close.icon{action (mut globals.flash.alert) ''} + = globals.flash.alert + if globals.flash.notice + .ui.floating.info.message + i.close.icon{action (mut globals.flash.notice) ''} + = globals.flash.notice + = outlet + /if globals.preload_resources_fulfilled + / = outlet + /else + / .ui.icon.message + / i.notched.circle.loading.icon + / .content + / .header Preloading resources... + / p Loading resources globally required before continuing with the application. + /button.basic.button{ action 'debug'} debug += outlet 'modal' + diff --git a/tests/dummy/app/templates/components/.gitkeep b/tests/dummy/app/templates/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/global/-top-menu.emblem b/tests/dummy/app/templates/global/-top-menu.emblem new file mode 100644 index 0000000..f058471 --- /dev/null +++ b/tests/dummy/app/templates/global/-top-menu.emblem @@ -0,0 +1,95 @@ +.ui.grid: .computer.tablet.only.row: .ui.inverted.menu.fixed + = link-to 'index' class='header item' + img src='/panda/app/assets/favicons/source.svg' + if globals.current_user + if (can 'read' 'model' 'panda/project') + = link-to 'project' class='item' + span= t 'models.plural.project' + /= link-to 'tracker-subject.index' class='item' + span= t 'models.plural.tracker_subject' + if (can 'manage' 'model' 'panda/organization') + = link-to 'organization' class='item' + span= t 'models.plural.organization' + if (can 'manage' 'model' 'user') + = link-to 'user' class='item' + span= t 'models.plural.user' + = link-to 'event' class='item' + span= t 'models.plural.event' + = link-to 'action-list' class='item' + span= t 'action_list.title' + if (any-permission-starts-with 'source-file') + a.computer.only.item href="/source_files/": span= t 'models.plural.source_file' + if (has-permission-of-type 'adapter') + ui-dropdown class='item' + span Adapters + i.dropdown.icon + .menu + each globals.adapters as |adapter| + a.item href="{{concat '/adapters/' adapter.id}}" class='item' + span= adapter.title + .right.menu + if globals.current_user + ui-dropdown class='item' + span= globals.current_user.nickname + i.dropdown.icon + .menu + = link-to 'user.profile' class='item' + i.user.icon + span.text= t 'user.profile' + = link-to 'manual' class='item' + i.book.icon + span.text= t 'manual.title' + a.item{ action 'signOut'} + i.toggle.right.icon + span.text= t 'user.sign_out' + else + = link-to 'Login' 'login' class='item' +.ui.grid: .mobile.only.row + .ui.inverted.menu.fixed + = link-to 'index' class='header item' + img src='/panda/app/assets/favicons/source.svg' + if globals.current_user + ui-dropdown class='item' + span Menu + i.dropdown.icon + .menu + if (can 'read' 'model' 'panda/project') + = link-to 'project' class='item' + span= t 'models.plural.project' + /= link-to 'tracker-subject.index' class='item' + span= t 'models.plural.tracker_subject' + if (can 'manage' 'model' 'panda/organization') + = link-to 'organization' class='item' + span= t 'models.plural.organization' + if (can 'manage' 'model' 'user') + = link-to 'user' class='item' + span= t 'models.plural.user' + = link-to 'event' class='item' + span= t 'models.plural.event' + if (any-permission-starts-with 'source-file') + a.item href="/source_files/": span= t 'models.plural.source_file' + if (has-permission-of-type 'adapter') + .item + i.dropdown.icon + span.text Adapters + .right.menu + each globals.adapters as |adapter| + a.item href="{{concat '/adapters/' adapter.id}}" class='item' + span= adapter.title + .right.menu + if globals.current_user + ui-dropdown class='item' + span= globals.current_user.nickname + i.dropdown.icon + .menu + = link-to 'user.profile' class='item' + i.user.icon + span.text= t 'user.profile' + = link-to 'manual' class='item' + i.book.icon + span.text= t 'manual.title' + a.item{ action 'signOut'} + i.toggle.right.icon + span.text= t 'user.sign_out' + else + = link-to 'Login' 'login' class='item' diff --git a/tests/dummy/app/templates/index.emblem b/tests/dummy/app/templates/index.emblem new file mode 100644 index 0000000..696f773 --- /dev/null +++ b/tests/dummy/app/templates/index.emblem @@ -0,0 +1,14 @@ +.ui.container + = page-title "Panda home" + br + br + br + img.ui.centered.image src="/panda/app/assets/favicons/android-chrome-192x192.png" + + + .ui.header TODO + .ui.list + .item Fix markdown show (action extra info) + .item Add identifiers view for dunlop project info view + + .ui.header DONE diff --git a/tests/dummy/app/templates/loading.emblem b/tests/dummy/app/templates/loading.emblem new file mode 100644 index 0000000..4be4a75 --- /dev/null +++ b/tests/dummy/app/templates/loading.emblem @@ -0,0 +1,6 @@ +.ui.segment + .ui.active.dimmer + .ui.massive.text.loader Loading + p + p + p diff --git a/tests/dummy/app/templates/login.emblem b/tests/dummy/app/templates/login.emblem new file mode 100644 index 0000000..0597d4d --- /dev/null +++ b/tests/dummy/app/templates/login.emblem @@ -0,0 +1,6 @@ +br +br +.ui.container: .ui.middle.aligned.center.aligned.grid.login-form: .column + .ui.image.header: .content Login to Panda + .ui.large.form.segment= login-form + img.ui.centered.image src="/panda/app/assets/favicons/android-chrome-192x192.png" diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js new file mode 100644 index 0000000..e7507bd --- /dev/null +++ b/tests/dummy/config/environment.js @@ -0,0 +1,60 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(environment) { + var ENV = { + modulePrefix: 'dummy', + environment: environment, + rootURL: '/panda/app/', + locationType: 'auto', + applicationName: "Panda", + apiHost: '', + apiNamespace: 'panda/api/v1', + exportApplicationGlobal: true, + moment: { + includeLocales: ['nl'] + }, + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. 'with-controller': true + }, + EXTEND_PROTOTYPES: { + // Prevent Ember Data from overriding Date.parse. + Date: false + } + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + } + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + ENV.apiHost = "http://localhost:3006"; // when using the stand alone ember server implementation with rails server explicitly as api host (foreman run web) + //ENV.apiHost = "https://kpn-staging.panda-suite.com"; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + } + + if (environment === 'production') { + + } + + return ENV; +}; diff --git a/tests/dummy/config/targets.js b/tests/dummy/config/targets.js new file mode 100644 index 0000000..df7664c --- /dev/null +++ b/tests/dummy/config/targets.js @@ -0,0 +1,9 @@ +/* eslint-env node */ +module.exports = { + browsers: [ + 'ie 9', + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions' + ] +}; diff --git a/tests/dummy/public/assets/favicons/android-chrome-192x192.png b/tests/dummy/public/assets/favicons/android-chrome-192x192.png new file mode 100644 index 0000000..3227e81 Binary files /dev/null and b/tests/dummy/public/assets/favicons/android-chrome-192x192.png differ diff --git a/tests/dummy/public/assets/favicons/android-chrome-512x512.png b/tests/dummy/public/assets/favicons/android-chrome-512x512.png new file mode 100644 index 0000000..1958fdd Binary files /dev/null and b/tests/dummy/public/assets/favicons/android-chrome-512x512.png differ diff --git a/tests/dummy/public/assets/favicons/apple-touch-icon.png b/tests/dummy/public/assets/favicons/apple-touch-icon.png new file mode 100644 index 0000000..36f6d7e Binary files /dev/null and b/tests/dummy/public/assets/favicons/apple-touch-icon.png differ diff --git a/tests/dummy/public/assets/favicons/browserconfig.xml b/tests/dummy/public/assets/favicons/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/tests/dummy/public/assets/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/tests/dummy/public/assets/favicons/favicon-16x16.png b/tests/dummy/public/assets/favicons/favicon-16x16.png new file mode 100644 index 0000000..53167e3 Binary files /dev/null and b/tests/dummy/public/assets/favicons/favicon-16x16.png differ diff --git a/tests/dummy/public/assets/favicons/favicon-32x32.png b/tests/dummy/public/assets/favicons/favicon-32x32.png new file mode 100644 index 0000000..0a8582c Binary files /dev/null and b/tests/dummy/public/assets/favicons/favicon-32x32.png differ diff --git a/tests/dummy/public/assets/favicons/favicon.ico b/tests/dummy/public/assets/favicons/favicon.ico new file mode 100644 index 0000000..42e16e2 Binary files /dev/null and b/tests/dummy/public/assets/favicons/favicon.ico differ diff --git a/tests/dummy/public/assets/favicons/manifest.json b/tests/dummy/public/assets/favicons/manifest.json new file mode 100644 index 0000000..4fbe181 --- /dev/null +++ b/tests/dummy/public/assets/favicons/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/tests/dummy/public/assets/favicons/mstile-150x150.png b/tests/dummy/public/assets/favicons/mstile-150x150.png new file mode 100644 index 0000000..2859780 Binary files /dev/null and b/tests/dummy/public/assets/favicons/mstile-150x150.png differ diff --git a/tests/dummy/public/assets/favicons/safari-pinned-tab.svg b/tests/dummy/public/assets/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..4b00477 --- /dev/null +++ b/tests/dummy/public/assets/favicons/safari-pinned-tab.svg @@ -0,0 +1,52 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/tests/dummy/public/assets/favicons/source.svg b/tests/dummy/public/assets/favicons/source.svg new file mode 100644 index 0000000..4bf258a --- /dev/null +++ b/tests/dummy/public/assets/favicons/source.svg @@ -0,0 +1,137 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/tests/dummy/public/assets/icons/svg/logical-conjunction.svg b/tests/dummy/public/assets/icons/svg/logical-conjunction.svg new file mode 100644 index 0000000..8c69f92 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-conjunction.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg b/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg new file mode 100644 index 0000000..e50fd3e --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-disjunction.svg b/tests/dummy/public/assets/icons/svg/logical-disjunction.svg new file mode 100644 index 0000000..433e0f9 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-disjunction.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg b/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg new file mode 100644 index 0000000..73cea24 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg b/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg new file mode 100644 index 0000000..28fe65f --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/manual/action-offsets/non-rooted.png b/tests/dummy/public/assets/manual/action-offsets/non-rooted.png new file mode 100644 index 0000000..a8353fc Binary files /dev/null and b/tests/dummy/public/assets/manual/action-offsets/non-rooted.png differ diff --git a/tests/dummy/public/assets/manual/action-offsets/rooted-critical-path.png b/tests/dummy/public/assets/manual/action-offsets/rooted-critical-path.png new file mode 100644 index 0000000..3ce8fb7 Binary files /dev/null and b/tests/dummy/public/assets/manual/action-offsets/rooted-critical-path.png differ diff --git a/tests/dummy/public/assets/manual/action-offsets/rooted-non-critical-path.png b/tests/dummy/public/assets/manual/action-offsets/rooted-non-critical-path.png new file mode 100644 index 0000000..0b7acdd Binary files /dev/null and b/tests/dummy/public/assets/manual/action-offsets/rooted-non-critical-path.png differ diff --git a/tests/dummy/public/assets/manual/manual-index.png b/tests/dummy/public/assets/manual/manual-index.png new file mode 100644 index 0000000..40d338c Binary files /dev/null and b/tests/dummy/public/assets/manual/manual-index.png differ diff --git a/tests/dummy/public/assets/manual/workflow-action-definition-confirmation.png b/tests/dummy/public/assets/manual/workflow-action-definition-confirmation.png new file mode 100644 index 0000000..c1f3186 Binary files /dev/null and b/tests/dummy/public/assets/manual/workflow-action-definition-confirmation.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-144x144.png b/tests/dummy/public/assets/panda/android-icon-144x144.png new file mode 100644 index 0000000..9ac1227 Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-144x144.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-192x192.png b/tests/dummy/public/assets/panda/android-icon-192x192.png new file mode 100644 index 0000000..6a42f4c Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-192x192.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-36x36.png b/tests/dummy/public/assets/panda/android-icon-36x36.png new file mode 100644 index 0000000..ab25d93 Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-36x36.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-48x48.png b/tests/dummy/public/assets/panda/android-icon-48x48.png new file mode 100644 index 0000000..22ccb95 Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-48x48.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-72x72.png b/tests/dummy/public/assets/panda/android-icon-72x72.png new file mode 100644 index 0000000..c492205 Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-72x72.png differ diff --git a/tests/dummy/public/assets/panda/android-icon-96x96.png b/tests/dummy/public/assets/panda/android-icon-96x96.png new file mode 100644 index 0000000..cbc06d4 Binary files /dev/null and b/tests/dummy/public/assets/panda/android-icon-96x96.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-114x114.png b/tests/dummy/public/assets/panda/apple-icon-114x114.png new file mode 100644 index 0000000..fd26c94 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-114x114.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-120x120.png b/tests/dummy/public/assets/panda/apple-icon-120x120.png new file mode 100644 index 0000000..3eb7d82 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-120x120.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-144x144.png b/tests/dummy/public/assets/panda/apple-icon-144x144.png new file mode 100644 index 0000000..9ac1227 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-144x144.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-152x152.png b/tests/dummy/public/assets/panda/apple-icon-152x152.png new file mode 100644 index 0000000..7267410 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-152x152.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-180x180.png b/tests/dummy/public/assets/panda/apple-icon-180x180.png new file mode 100644 index 0000000..eb9383d Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-180x180.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-57x57.png b/tests/dummy/public/assets/panda/apple-icon-57x57.png new file mode 100644 index 0000000..ce2a6be Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-57x57.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-60x60.png b/tests/dummy/public/assets/panda/apple-icon-60x60.png new file mode 100644 index 0000000..48f9ed7 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-60x60.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-72x72.png b/tests/dummy/public/assets/panda/apple-icon-72x72.png new file mode 100644 index 0000000..c492205 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-72x72.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-76x76.png b/tests/dummy/public/assets/panda/apple-icon-76x76.png new file mode 100644 index 0000000..409f991 Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-76x76.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon-precomposed.png b/tests/dummy/public/assets/panda/apple-icon-precomposed.png new file mode 100644 index 0000000..573fc0e Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon-precomposed.png differ diff --git a/tests/dummy/public/assets/panda/apple-icon.png b/tests/dummy/public/assets/panda/apple-icon.png new file mode 100644 index 0000000..573fc0e Binary files /dev/null and b/tests/dummy/public/assets/panda/apple-icon.png differ diff --git a/tests/dummy/public/assets/panda/browserconfig.xml b/tests/dummy/public/assets/panda/browserconfig.xml new file mode 100644 index 0000000..c554148 --- /dev/null +++ b/tests/dummy/public/assets/panda/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/tests/dummy/public/assets/panda/favicon-16x16.png b/tests/dummy/public/assets/panda/favicon-16x16.png new file mode 100644 index 0000000..9b58a70 Binary files /dev/null and b/tests/dummy/public/assets/panda/favicon-16x16.png differ diff --git a/tests/dummy/public/assets/panda/favicon-32x32.png b/tests/dummy/public/assets/panda/favicon-32x32.png new file mode 100644 index 0000000..ee680c5 Binary files /dev/null and b/tests/dummy/public/assets/panda/favicon-32x32.png differ diff --git a/tests/dummy/public/assets/panda/favicon-96x96.png b/tests/dummy/public/assets/panda/favicon-96x96.png new file mode 100644 index 0000000..cbc06d4 Binary files /dev/null and b/tests/dummy/public/assets/panda/favicon-96x96.png differ diff --git a/tests/dummy/public/assets/panda/favicon.ico b/tests/dummy/public/assets/panda/favicon.ico new file mode 100644 index 0000000..aa8c27e Binary files /dev/null and b/tests/dummy/public/assets/panda/favicon.ico differ diff --git a/tests/dummy/public/assets/panda/manifest.json b/tests/dummy/public/assets/panda/manifest.json new file mode 100644 index 0000000..013d4a6 --- /dev/null +++ b/tests/dummy/public/assets/panda/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/tests/dummy/public/assets/panda/ms-icon-144x144.png b/tests/dummy/public/assets/panda/ms-icon-144x144.png new file mode 100644 index 0000000..9ac1227 Binary files /dev/null and b/tests/dummy/public/assets/panda/ms-icon-144x144.png differ diff --git a/tests/dummy/public/assets/panda/ms-icon-150x150.png b/tests/dummy/public/assets/panda/ms-icon-150x150.png new file mode 100644 index 0000000..4f1ef4a Binary files /dev/null and b/tests/dummy/public/assets/panda/ms-icon-150x150.png differ diff --git a/tests/dummy/public/assets/panda/ms-icon-310x310.png b/tests/dummy/public/assets/panda/ms-icon-310x310.png new file mode 100644 index 0000000..4b1de3a Binary files /dev/null and b/tests/dummy/public/assets/panda/ms-icon-310x310.png differ diff --git a/tests/dummy/public/assets/panda/ms-icon-70x70.png b/tests/dummy/public/assets/panda/ms-icon-70x70.png new file mode 100644 index 0000000..80d11b1 Binary files /dev/null and b/tests/dummy/public/assets/panda/ms-icon-70x70.png differ diff --git a/tests/dummy/public/assets/panda/panda-base.png b/tests/dummy/public/assets/panda/panda-base.png new file mode 100644 index 0000000..d00219b Binary files /dev/null and b/tests/dummy/public/assets/panda/panda-base.png differ diff --git a/tests/dummy/public/assets/panda/panda-base.svg b/tests/dummy/public/assets/panda/panda-base.svg new file mode 100644 index 0000000..2f6bfa3 --- /dev/null +++ b/tests/dummy/public/assets/panda/panda-base.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/panda/panda-base_backup.svg b/tests/dummy/public/assets/panda/panda-base_backup.svg new file mode 100644 index 0000000..2887e61 --- /dev/null +++ b/tests/dummy/public/assets/panda/panda-base_backup.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/team-panda/Panda svg.svg b/tests/dummy/public/assets/team-panda/Panda svg.svg new file mode 100644 index 0000000..892c8c2 --- /dev/null +++ b/tests/dummy/public/assets/team-panda/Panda svg.svg @@ -0,0 +1,118 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/team-panda/team-panda.png b/tests/dummy/public/assets/team-panda/team-panda.png new file mode 100644 index 0000000..97a4ef2 Binary files /dev/null and b/tests/dummy/public/assets/team-panda/team-panda.png differ diff --git a/tests/dummy/public/crossdomain.xml b/tests/dummy/public/crossdomain.xml new file mode 100644 index 0000000..0c16a7a --- /dev/null +++ b/tests/dummy/public/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/tests/dummy/public/robots.txt b/tests/dummy/public/robots.txt new file mode 100644 index 0000000..f591645 --- /dev/null +++ b/tests/dummy/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/tests/helpers/destroy-app.js b/tests/helpers/destroy-app.js new file mode 100644 index 0000000..c3d4d1a --- /dev/null +++ b/tests/helpers/destroy-app.js @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export default function destroyApp(application) { + Ember.run(application, 'destroy'); +} diff --git a/tests/helpers/module-for-acceptance.js b/tests/helpers/module-for-acceptance.js new file mode 100644 index 0000000..5a1a583 --- /dev/null +++ b/tests/helpers/module-for-acceptance.js @@ -0,0 +1,23 @@ +import { module } from 'qunit'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; +import destroyApp from '../helpers/destroy-app'; + +const { RSVP: { resolve } } = Ember; + +export default function(name, options = {}) { + module(name, { + beforeEach() { + this.application = startApp(); + + if (options.beforeEach) { + return options.beforeEach.apply(this, arguments); + } + }, + + afterEach() { + let afterEach = options.afterEach && options.afterEach.apply(this, arguments); + return resolve(afterEach).then(() => destroyApp(this.application)); + } + }); +} diff --git a/tests/helpers/resolver.js b/tests/helpers/resolver.js new file mode 100644 index 0000000..b208d38 --- /dev/null +++ b/tests/helpers/resolver.js @@ -0,0 +1,11 @@ +import Resolver from '../../resolver'; +import config from '../../config/environment'; + +const resolver = Resolver.create(); + +resolver.namespace = { + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix +}; + +export default resolver; diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js new file mode 100644 index 0000000..9a605eb --- /dev/null +++ b/tests/helpers/start-app.js @@ -0,0 +1,15 @@ +import Ember from 'ember'; +import Application from '../../app'; +import config from '../../config/environment'; + +export default function startApp(attrs) { + let attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + + return Ember.run(() => { + let application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + return application; + }); +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..5209b85 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,33 @@ + + + + + + Dummy Tests + + + + {{content-for "head"}} + {{content-for "test-head"}} + + + + + + {{content-for "head-footer"}} + {{content-for "test-head-footer"}} + + + {{content-for "body"}} + {{content-for "test-body"}} + + + + + + + + {{content-for "body-footer"}} + {{content-for "test-body-footer"}} + + diff --git a/tests/integration/.gitkeep b/tests/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/components/action-list/organization-filter-test.coffee b/tests/integration/components/action-list/organization-filter-test.coffee new file mode 100644 index 0000000..ec2c9ef --- /dev/null +++ b/tests/integration/components/action-list/organization-filter-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'action-list/organization-filter', 'Integration | Component | action list/organization filter', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{action-list/organization-filter}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#action-list/organization-filter}} + template block text + {{/action-list/organization-filter}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/action-list/project-filter-test.coffee b/tests/integration/components/action-list/project-filter-test.coffee new file mode 100644 index 0000000..cdc5dc9 --- /dev/null +++ b/tests/integration/components/action-list/project-filter-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'action-list/project-filter', 'Integration | Component | action list/project filter', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{action-list/project-filter}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#action-list/project-filter}} + template block text + {{/action-list/project-filter}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/panda-image-test.coffee b/tests/integration/components/panda-image-test.coffee new file mode 100644 index 0000000..bb91ade --- /dev/null +++ b/tests/integration/components/panda-image-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'panda-image', 'Integration | Component | panda image', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{panda-image}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#panda-image}} + template block text + {{/panda-image}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/project/edit-active-project-scenario-test.coffee b/tests/integration/components/project/edit-active-project-scenario-test.coffee new file mode 100644 index 0000000..059c741 --- /dev/null +++ b/tests/integration/components/project/edit-active-project-scenario-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'project/edit-active-project-scenario', 'Integration | Component | project/edit active project scenario', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{project/edit-active-project-scenario}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#project/edit-active-project-scenario}} + template block text + {{/project/edit-active-project-scenario}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/project/instance-action-value-display-test.coffee b/tests/integration/components/project/instance-action-value-display-test.coffee new file mode 100644 index 0000000..5052bdf --- /dev/null +++ b/tests/integration/components/project/instance-action-value-display-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'project/instance-action-value-display', 'Integration | Component | project/instance action value display', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{project/instance-action-value-display}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#project/instance-action-value-display}} + template block text + {{/project/instance-action-value-display}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/state-badge-test.coffee b/tests/integration/components/state-badge-test.coffee new file mode 100644 index 0000000..32661d5 --- /dev/null +++ b/tests/integration/components/state-badge-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'state-badge', 'Integration | Component | state badge', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{state-badge}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#state-badge}} + template block text + {{/state-badge}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/ui-multi-select-test.coffee b/tests/integration/components/ui-multi-select-test.coffee new file mode 100644 index 0000000..92a8932 --- /dev/null +++ b/tests/integration/components/ui-multi-select-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'ui-multi-select', 'Integration | Component | ui multi select', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{ui-multi-select}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#ui-multi-select}} + template block text + {{/ui-multi-select}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/value-type-display-test.coffee b/tests/integration/components/value-type-display-test.coffee new file mode 100644 index 0000000..c8f5997 --- /dev/null +++ b/tests/integration/components/value-type-display-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'value-type-display', 'Integration | Component | value type display', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{value-type-display}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#value-type-display}} + template block text + {{/value-type-display}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/test-helper.js b/tests/test-helper.js new file mode 100644 index 0000000..f219659 --- /dev/null +++ b/tests/test-helper.js @@ -0,0 +1,8 @@ +import resolver from './helpers/resolver'; +import { + setResolver +} from 'ember-qunit'; +import { start } from 'ember-cli-qunit'; + +setResolver(resolver); +start(); diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/controllers/action-list-test.coffee b/tests/unit/controllers/action-list-test.coffee new file mode 100644 index 0000000..afe2477 --- /dev/null +++ b/tests/unit/controllers/action-list-test.coffee @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit' + +moduleFor 'controller:action-list', { + # Specify the other units that are required for this test. + # needs: ['controller:foo'] +} + +# Replace this with your real tests. +test 'it exists', (assert) -> + controller = @subject() + assert.ok controller diff --git a/tests/unit/models/panda/action-event-test.coffee b/tests/unit/models/panda/action-event-test.coffee new file mode 100644 index 0000000..de4f0e9 --- /dev/null +++ b/tests/unit/models/panda/action-event-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/action-event', 'Unit | Model | panda/action event', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/adapter-test.coffee b/tests/unit/models/panda/adapter-test.coffee new file mode 100644 index 0000000..22d8b36 --- /dev/null +++ b/tests/unit/models/panda/adapter-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/adapter', 'Unit | Model | panda/adapter', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/organization-test.coffee b/tests/unit/models/panda/organization-test.coffee new file mode 100644 index 0000000..a2289ce --- /dev/null +++ b/tests/unit/models/panda/organization-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/organization', 'Unit | Model | panda/organization', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/tracker-subject-test.coffee b/tests/unit/models/panda/tracker-subject-test.coffee new file mode 100644 index 0000000..40b0d64 --- /dev/null +++ b/tests/unit/models/panda/tracker-subject-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/tracker-subject', 'Unit | Model | panda/tracker subject', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/workflow-action-connection-test.coffee b/tests/unit/models/panda/workflow-action-connection-test.coffee new file mode 100644 index 0000000..246eb56 --- /dev/null +++ b/tests/unit/models/panda/workflow-action-connection-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/workflow-action-connection', 'Unit | Model | panda/workflow action connection', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/workflow-instance-type-test.coffee b/tests/unit/models/panda/workflow-instance-type-test.coffee new file mode 100644 index 0000000..0ea4385 --- /dev/null +++ b/tests/unit/models/panda/workflow-instance-type-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/workflow-instance-type', 'Unit | Model | panda/workflow instance type', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/routes/action-list-test.coffee b/tests/unit/routes/action-list-test.coffee new file mode 100644 index 0000000..eb09182 --- /dev/null +++ b/tests/unit/routes/action-list-test.coffee @@ -0,0 +1,10 @@ +import { moduleFor, test } from 'ember-qunit' + +moduleFor 'route:action-list', 'Unit | Route | action list', { + # Specify the other units that are required for this test. + # needs: ['controller:foo'] +} + +test 'it exists', (assert) -> + route = @subject() + assert.ok route diff --git a/tests/unit/utils/panda-route-setup-test.coffee b/tests/unit/utils/panda-route-setup-test.coffee new file mode 100644 index 0000000..6972e62 --- /dev/null +++ b/tests/unit/utils/panda-route-setup-test.coffee @@ -0,0 +1,9 @@ +import pandaRouteSetup from '../../../utils/panda-route-setup' +import { module, test } from 'qunit' + +module 'Unit | Utility | panda route setup' + +# Replace this with your real tests. +test 'it works', (assert) -> + result = pandaRouteSetup() + assert.ok result diff --git a/vendor/.gitkeep b/vendor/.gitkeep new file mode 100644 index 0000000..e69de29