From 73d55f9e43b73423203c96dac694f923deb34c76 Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Mon, 29 Jan 2018 23:42:20 +0100 Subject: [PATCH] wip --- app/controllers/custom_actions_controller.rb | 1 + app/views/custom_actions/_form.html.erb | 7 +- config/locales/js-en.yml | 3 + .../20180123092002_add_custom_actions.rb | 8 +++ frontend/app/angular4-modules.ts | 30 ++++---- .../gon-ref.ts} | 39 +++-------- .../add-section-dropdown.component.html | 12 ++++ .../add-section-dropdown.component.ts | 67 ++++++++++++++++++ .../hide-section-link.component.html | 8 +++ .../hide-section-link.component.ts} | 38 ++++------ .../hide-section/hide-section.component.ts | 15 ++-- .../hide-section/hide-section.service.ts | 69 +++++++++++++++++++ .../hide-sections.component.html | 5 -- .../work_packages/workflow_buttons_spec.rb | 45 ++++-------- spec/support/pages/admin/new_custom_action.rb | 55 +++++++++++++++ 15 files changed, 284 insertions(+), 118 deletions(-) create mode 100644 db/migrate/20180123092002_add_custom_actions.rb rename frontend/app/components/common/{hide-sections/hide-sections.service.ts => gon-ref/gon-ref.ts} (65%) create mode 100644 frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.html create mode 100644 frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.ts create mode 100644 frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.html rename frontend/app/components/common/{hide-sections/hide-sections.component.ts => hide-section/hide-section-link/hide-section-link.component.ts} (57%) create mode 100644 frontend/app/components/common/hide-section/hide-section.service.ts delete mode 100644 frontend/app/components/common/hide-sections/hide-sections.component.html create mode 100644 spec/support/pages/admin/new_custom_action.rb diff --git a/app/controllers/custom_actions_controller.rb b/app/controllers/custom_actions_controller.rb index 1b5128cde4..3fee38b27b 100644 --- a/app/controllers/custom_actions_controller.rb +++ b/app/controllers/custom_actions_controller.rb @@ -29,6 +29,7 @@ class CustomActionsController < ApplicationController before_action :require_admin + layout 'admin' def index @custom_actions = CustomAction.order_by_name diff --git a/app/views/custom_actions/_form.html.erb b/app/views/custom_actions/_form.html.erb index cdb4642615..e5a2cd11d2 100644 --- a/app/views/custom_actions/_form.html.erb +++ b/app/views/custom_actions/_form.html.erb @@ -1,7 +1,7 @@ <% gon.push({ - actions: @custom_action.all_actions.map(&:key) + hide_sections: @custom_action.all_actions.map { |a| { key: a.key, label: a.human_name } } }) %> @@ -16,7 +16,7 @@ gon.push({ <%= t('custom_actions.actions') %> - + <% @custom_action.all_actions.each do |action| %>
@@ -47,8 +47,11 @@ gon.push({ container_class: '-slim', step: 'any' %> <% end %> +
<% end %>
+ + diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 9d1b3dbaa7..c1df63cf64 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -39,6 +39,9 @@ en: browser_error: "Your browser doesn't support copying to clipboard. Please copy the selected text manually." copied_successful: "Sucessfully copied to clipboard!" + custom_actions: + add: "Add" + button_add_watcher: "Add watcher" button_back_to_list_view: "Back to list view" button_cancel: "Cancel" diff --git a/db/migrate/20180123092002_add_custom_actions.rb b/db/migrate/20180123092002_add_custom_actions.rb new file mode 100644 index 0000000000..5eb0d2b04f --- /dev/null +++ b/db/migrate/20180123092002_add_custom_actions.rb @@ -0,0 +1,8 @@ +class AddCustomActions < ActiveRecord::Migration[5.0] + def change + create_table :custom_actions do |t| + t.string :name + t.text :actions + end + end +end diff --git a/frontend/app/angular4-modules.ts b/frontend/app/angular4-modules.ts index e56de81dfb..47ff384c83 100644 --- a/frontend/app/angular4-modules.ts +++ b/frontend/app/angular4-modules.ts @@ -69,20 +69,11 @@ import {WpWorkflowButtonComponent} from 'core-components/wp-workflow-buttons/wp- import {WpWorkflowButtonsComponent} from 'core-components/wp-workflow-buttons/wp-workflow-buttons.component'; import {HalRequestService} from 'core-components/api/api-v3/hal-request/hal-request.service'; import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service'; -import {HideSectionsComponent} from 'core-components/common/hide-sections/hide-sections.component'; -import {Angular4WrapperComponent} from 'core-components/common/angular4-wrapper/angular4-wrapper.component'; import {HideSectionComponent} from 'core-components/common/hide-section/hide-section.component'; -import {HideSectionsService} from 'core-components/common/hide-sections/hide-sections.service'; - -@NgModule({ - declarations: [ - Angular4WrapperComponent - ], - exports: [ - Angular4WrapperComponent - ] -}) -export class Angular4Wrapper { } +import {HideSectionService} from 'core-components/common/hide-section/hide-section.service'; +import {AddSectionDropdownComponent} from 'core-components/common/hide-section/add-section-dropdown/add-section-dropdown.component'; +import {HideSectionLinkComponent} from 'core-components/common/hide-section/hide-section-link/hide-section-link.component'; +import {GonRef} from 'core-components/common/gon-ref/gon-ref'; @NgModule({ imports: [ @@ -91,7 +82,8 @@ export class Angular4Wrapper { } FormsModule ], providers: [ - HideSectionsService, + GonRef, + HideSectionService, WorkPackagesTableControllerHolder, upgradeService('wpRelations', WorkPackageRelationsService), upgradeService('states', States), @@ -132,16 +124,18 @@ export class Angular4Wrapper { } SortHeaderDirective, HasDropdownMenuDirective, WpInlineCreateDirectiveUpgraded, - HideSectionsComponent, - HideSectionComponent + HideSectionComponent, + HideSectionLinkComponent, + AddSectionDropdownComponent ], entryComponents: [ WorkPackageTablePaginationComponent, WorkPackagesTableController, TablePaginationComponent, WpWorkflowButtonsComponent, - HideSectionsComponent, - HideSectionComponent + HideSectionComponent, + HideSectionLinkComponent, + AddSectionDropdownComponent ] }) export class OpenProjectModule { diff --git a/frontend/app/components/common/hide-sections/hide-sections.service.ts b/frontend/app/components/common/gon-ref/gon-ref.ts similarity index 65% rename from frontend/app/components/common/hide-sections/hide-sections.service.ts rename to frontend/app/components/common/gon-ref/gon-ref.ts index c7346cccd5..e5bf1f716d 100644 --- a/frontend/app/components/common/hide-sections/hide-sections.service.ts +++ b/frontend/app/components/common/gon-ref/gon-ref.ts @@ -26,36 +26,19 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; -@Injectable() -export class HideSectionsService { - protected availableSections:string[] = []; - protected displayedSections:string[] = []; - - constructor() { - console.log('HideSectionsService constructed'); - } - - isDisplayed(name:string) { - return this.displayedSections.indexOf(name) > 0; - } - - hide(name:string) { - - } - - show(name:string) { - this.displayedSections.push(name); - _.remove(this.availableSections, (candidate) => candidate === name); - console.log(this.displayedSections); - } +interface GonWindow extends Window { + gon:{} +} - set all(available:string[]) { - this.availableSections = available; - } +function _gon() : any { + return ( window).gon; +} - get available() { - return this.availableSections; +@Injectable() +export class GonRef { + get(name:string) : any { + return _gon()[name]; } } diff --git a/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.html b/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.html new file mode 100644 index 0000000000..28f306790f --- /dev/null +++ b/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.html @@ -0,0 +1,12 @@ +
+ + + + + + +
+ diff --git a/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.ts b/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.ts new file mode 100644 index 0000000000..c91d17a739 --- /dev/null +++ b/frontend/app/components/common/hide-section/add-section-dropdown/add-section-dropdown.component.ts @@ -0,0 +1,67 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See doc/COPYRIGHT.rdoc for more details. +// ++ + +import {opUiComponentsModule} from '../../../../angular-modules'; +import {Component, Inject, OnInit} from '@angular/core'; +import {downgradeComponent} from '@angular/upgrade/static'; +import {HideSectionDefinition, HideSectionService} from 'core-components/common/hide-section/hide-section.service'; +import {I18nToken} from 'core-app/angular4-transition-utils'; + +@Component({ + selector: 'add-section-dropdown', + template: require('!!raw-loader!./add-section-dropdown.component.html') +}) +export class AddSectionDropdownComponent implements OnInit { + selectable:HideSectionDefinition[] = []; + turnedActive:HideSectionDefinition|null = null; + texts: { [key:string]: string } = {}; + + constructor(protected hideSections:HideSectionService, + @Inject(I18nToken) protected I18n:op.I18n) { + this.texts = { + placeholder: I18n.t('js.placeholders.default'), + add: I18n.t('js.custom_actions.add') + }; + } + + ngOnInit() { + this.selectable = this.hideSections.available; + } + + show() { + if (this.turnedActive) { + this.hideSections.show(this.turnedActive); + setTimeout(() => { this.turnedActive = null } ); + } + } +} + +opUiComponentsModule.directive( + 'addSectionDropdown', + downgradeComponent({component: AddSectionDropdownComponent}) +); diff --git a/frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.html b/frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.html new file mode 100644 index 0000000000..2006d3f0cf --- /dev/null +++ b/frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.html @@ -0,0 +1,8 @@ + + + diff --git a/frontend/app/components/common/hide-sections/hide-sections.component.ts b/frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.ts similarity index 57% rename from frontend/app/components/common/hide-sections/hide-sections.component.ts rename to frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.ts index c3932a8de2..97f2f93303 100644 --- a/frontend/app/components/common/hide-sections/hide-sections.component.ts +++ b/frontend/app/components/common/hide-section/hide-section-link/hide-section-link.component.ts @@ -26,45 +26,33 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import {opUiComponentsModule} from '../../../angular-modules'; +import {opUiComponentsModule} from '../../../../angular-modules'; import {Component} from '@angular/core'; import {OnInit, Input} from '@angular/core'; import {downgradeComponent} from '@angular/upgrade/static'; -import {HideSectionsService} from 'core-components/common/hide-sections/hide-sections.service'; +import {HideSectionService} from 'core-components/common/hide-section/hide-section.service'; @Component({ - selector: 'hide-sections', - template: require('!!raw-loader!./hide-sections.component.html') - + selector: 'hide-section-link', + template: require('!!raw-loader!./hide-section-link.component.html') }) -export class HideSectionsComponent implements OnInit { - selectable:string[] = []; - //active:string[] = ['Some']; - turnedActive:string; +export class HideSectionLinkComponent implements OnInit { + displayed:boolean = true; - @Input('availableSections') rawAvailableSections:string = ''; + @Input('sectionName') sectionName:string; - constructor(protected hideSections:HideSectionsService) { - } + constructor(protected hideSections:HideSectionService) {} ngOnInit() { - // using _.map(this.rawAvailableSections.split(','), _.trim) works but the typescript compiler believes - // the return values to be boolean[]; - this.hideSections.all = _.map(this.rawAvailableSections.split(','), (name) => _.trim(name)); - this.selectable = this.hideSections.available; } - show(name:string) { - this.hideSections.show(name); + hideSection() { + this.hideSections.hideByName(this.sectionName); + return false; } - - //sectionDisplayed(name:string) { - // console.log(name); - // return this.hideSections.isDisplayed(name); - //} } opUiComponentsModule.directive( - 'hideSections', - downgradeComponent({component: HideSectionsComponent}) + 'hideSectionLink', + downgradeComponent({component: HideSectionLinkComponent}) ); diff --git a/frontend/app/components/common/hide-section/hide-section.component.ts b/frontend/app/components/common/hide-section/hide-section.component.ts index 67c4669dfd..4127e996a0 100644 --- a/frontend/app/components/common/hide-section/hide-section.component.ts +++ b/frontend/app/components/common/hide-section/hide-section.component.ts @@ -30,31 +30,26 @@ import {opUiComponentsModule} from '../../../angular-modules'; import {Component} from '@angular/core'; import {OnInit, Input} from '@angular/core'; import {downgradeComponent} from '@angular/upgrade/static'; -import {HideSectionsService} from 'core-components/common/hide-sections/hide-sections.service'; +import {HideSectionService} from 'core-components/common/hide-section/hide-section.service'; @Component({ selector: 'hide-section', - template: '' + - 'Remove' + template: '' + }) export class HideSectionComponent implements OnInit { displayed:boolean = true; @Input('sectionName') sectionName:string; - constructor(protected hideSections:HideSectionsService) { + constructor(protected hideSection:HideSectionService) { } ngOnInit() { } isDisplayed() { - //console.log('sectionDisplayed'); - return this.hideSections.isDisplayed(this.sectionName); - } - - hideSection() { - this.hideSections.hide(this.sectionName); + return this.hideSection.isDisplayed(this.sectionName); } } diff --git a/frontend/app/components/common/hide-section/hide-section.service.ts b/frontend/app/components/common/hide-section/hide-section.service.ts new file mode 100644 index 0000000000..7dd76424e8 --- /dev/null +++ b/frontend/app/components/common/hide-section/hide-section.service.ts @@ -0,0 +1,69 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See doc/COPYRIGHT.rdoc for more details. +// ++ + +import {Injectable} from '@angular/core'; +import {GonRef} from 'core-components/common/gon-ref/gon-ref'; + +export interface HideSectionDefinition { + key: string, + label: string +} + +@Injectable() +export class HideSectionService { + protected availableSections:HideSectionDefinition[] = []; + protected displayedSections:HideSectionDefinition[] = []; + + constructor(protected gonRef:GonRef) { + this.availableSections = gonRef.get('hideSections').sort((a:HideSectionDefinition, b:HideSectionDefinition) => a.label.localeCompare(b.label)); + } + + isDisplayed(key:string) { + return _.some(this.displayedSections, (candidate) => candidate.key === key); + } + + hide(key:string) { + let section = _.remove(this.displayedSections, (candidate) => candidate.key === key); + this.availableSections.push(_.first(section)); + this.availableSections.sort((a, b) => a.label.localeCompare(b.label)); + } + + show(section:HideSectionDefinition) { + _.remove(this.availableSections, (candidate) => candidate === section); + this.displayedSections.push(section); + } + + get available() { + return this.availableSections; + } + + hideByName(sectionName:string) { + let section = _.remove(this.displayedSections, (candidate) => candidate.key === sectionName); + this.availableSections.push(_.first(section)); + this.availableSections.sort((a, b) => a.label.localeCompare(b.label)); + } +} diff --git a/frontend/app/components/common/hide-sections/hide-sections.component.html b/frontend/app/components/common/hide-sections/hide-sections.component.html deleted file mode 100644 index 4ea81d0ed1..0000000000 --- a/frontend/app/components/common/hide-sections/hide-sections.component.html +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/spec/features/work_packages/workflow_buttons_spec.rb b/spec/features/work_packages/workflow_buttons_spec.rb index bb261658c4..4e7b5f14ef 100644 --- a/spec/features/work_packages/workflow_buttons_spec.rb +++ b/spec/features/work_packages/workflow_buttons_spec.rb @@ -76,6 +76,7 @@ describe 'Workflow buttons', type: :feature, js: true do role: role, type: work_package.type) end + let(:ca_page) { Pages::Admin::NewCustomAction.new } before do login_as(admin) @@ -93,13 +94,9 @@ describe 'Workflow buttons', type: :feature, js: true do click_link 'Custom action' end - fill_in 'Name', with: 'Unassign' - - within '#custom-actions-form--actions' do - select '-', from: 'Assignee' - end - - click_button 'Create' + ca_page.set_name('Unassign') + ca_page.add_action('Assignee', '-') + ca_page.create expect(page) .to have_current_path(custom_actions_path) @@ -113,13 +110,9 @@ describe 'Workflow buttons', type: :feature, js: true do click_link 'Custom action' end - fill_in 'Name', with: 'Close' - - within '#custom-actions-form--actions' do - select 'Close', from: 'Status' - end - - click_button 'Create' + ca_page.set_name('Close') + ca_page.add_action('Status', 'Close') + ca_page.create expect(page) .to have_current_path(custom_actions_path) @@ -133,13 +126,9 @@ describe 'Workflow buttons', type: :feature, js: true do click_link 'Custom action' end - fill_in 'Name', with: 'Escalate' - - within '#custom-actions-form--actions' do - select immediate_priority.name, from: 'Priority' - end - - click_button 'Create' + ca_page.set_name('Escalate') + ca_page.add_action('Priority', immediate_priority.name) + ca_page.create expect(page) .to have_current_path(custom_actions_path) @@ -153,15 +142,11 @@ describe 'Workflow buttons', type: :feature, js: true do click_link 'Custom action' end - fill_in 'Name', with: 'Reset' - - within '#custom-actions-form--actions' do - select default_priority.name, from: 'Priority' - select default_status.name, from: 'Status' - select user.name, from: 'Assignee' - end - - click_button 'Create' + ca_page.set_name('Reset') + ca_page.add_action('Priority', default_priority.name) + ca_page.add_action('Status', default_status.name) + ca_page.add_action('Assignee', user.name) + ca_page.create expect(page) .to have_current_path(custom_actions_path) diff --git a/spec/support/pages/admin/new_custom_action.rb b/spec/support/pages/admin/new_custom_action.rb new file mode 100644 index 0000000000..2d9166f3ed --- /dev/null +++ b/spec/support/pages/admin/new_custom_action.rb @@ -0,0 +1,55 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'support/pages/page' + +module Pages + module Admin + class NewCustomAction < ::Pages::Page + def set_name(name) + fill_in 'Name', with: name + end + + def add_action(name, value) + within '#custom-actions-form--actions' do + select name, from: 'Add' + + select value, from: name + end + end + + def create + click_button 'Create' + end + + def path + new_custom_action_path + end + end + end +end