Merge branch 'dev' into feature/grid_my_page

pull/6834/head
Jens Ulferts 6 years ago
commit 26a1050f8f
No known key found for this signature in database
GPG Key ID: 3CAA4B1182CF5308
  1. 15
      .travis.yml
  2. 2
      app/assets/javascripts/vendor/ckeditor/ckeditor.js
  3. 2
      app/assets/javascripts/vendor/ckeditor/ckeditor.js.map
  4. 1
      app/assets/stylesheets/content/_attributes_key_value.sass
  5. 10
      app/controllers/projects_controller.rb
  6. 3
      app/models/concerns/virtual_attribute.rb
  7. 19
      app/services/work_packages/update_ancestors_service.rb
  8. 6
      config/locales/crowdin/js-ru.yml
  9. 4
      config/locales/crowdin/ru.yml
  10. 2
      frontend/src/app/components/states.service.ts
  11. 2
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.html
  12. 6
      frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts
  13. 4
      frontend/src/app/components/work-packages/wp-single-view/wp-single-view.html
  14. 2
      frontend/src/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts
  15. 2
      frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts
  16. 6
      frontend/src/app/modules/attachments/attachments-upload/attachments-upload.component.ts
  17. 1
      frontend/src/app/modules/attachments/openproject-attachments.module.ts
  18. 19
      frontend/src/app/modules/plugins/plugin-context.ts
  19. 5
      frontend/src/app/modules/work_packages/openproject-work-packages.module.ts
  20. 2
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.html
  21. 5
      lib/api/v3/work_packages/work_package_representer.rb
  22. 5
      script/ci/cache_prepare.sh
  23. 52
      spec/features/work_packages/details/milestones_spec.rb
  24. 4
      spec/lib/api/v3/work_packages/work_package_representer_spec.rb
  25. 79
      spec/services/work_packages/destroy_service_integration_spec.rb

@ -89,6 +89,7 @@ jobs:
script:
- bash script/ci/setup.sh spec_legacy mysql
- bash script/ci/runner.sh spec_legacy 1 1
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'spec_legacy (1/1) - postgres'
script:
@ -99,6 +100,7 @@ jobs:
script:
- bash script/ci/setup.sh units mysql
- bash script/ci/runner.sh units 4 1
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'units (1/4) - postgres'
script:
@ -109,6 +111,7 @@ jobs:
script:
- bash script/ci/setup.sh units mysql
- bash script/ci/runner.sh units 4 2
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'units (2/4) - postgres'
script:
@ -119,6 +122,7 @@ jobs:
script:
- bash script/ci/setup.sh units mysql
- bash script/ci/runner.sh units 4 3
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'units (3/4) - postgres'
script:
@ -129,6 +133,7 @@ jobs:
script:
- bash script/ci/setup.sh units mysql
- bash script/ci/runner.sh units 4 4
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'units (4/4) - postgres'
script:
@ -139,6 +144,7 @@ jobs:
script:
- bash script/ci/setup.sh features mysql
- bash script/ci/runner.sh features 4 1
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'features (1/4) - postgres'
script:
@ -149,6 +155,7 @@ jobs:
script:
- bash script/ci/setup.sh features mysql
- bash script/ci/runner.sh features 4 2
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'features (2/4) - postgres'
script:
@ -159,6 +166,7 @@ jobs:
script:
- bash script/ci/setup.sh features mysql
- bash script/ci/runner.sh features 4 3
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'features (3/4) - postgres'
script:
@ -169,6 +177,7 @@ jobs:
script:
- bash script/ci/setup.sh features mysql
- bash script/ci/runner.sh features 4 4
if: env(SKIP_MYSQL_TESTING) IS blank
- stage: test
name: 'features (4/4) - postgres'
script:
@ -179,7 +188,7 @@ jobs:
script:
- bash script/ci/setup.sh plugins:units mysql
- bash script/ci/runner.sh plugins:units 1 1
if: head_branch !~ /^core\//
if: env(SKIP_MYSQL_TESTING) IS blank AND head_branch !~ /^core\//
- stage: test
name: 'plugins:units (1/1) - postgres'
script:
@ -191,7 +200,7 @@ jobs:
script:
- bash script/ci/setup.sh plugins:features mysql
- bash script/ci/runner.sh plugins:features 1 1
if: head_branch !~ /^core\//
if: env(SKIP_MYSQL_TESTING) IS blank AND head_branch !~ /^core\//
- stage: test
name: 'plugins:features (1/1) - postgres'
script:
@ -203,7 +212,7 @@ jobs:
script:
- bash script/ci/setup.sh plugins:cucumber mysql
- bash script/ci/runner.sh plugins:cucumber 1 1
if: head_branch !~ /^core\//
if: env(SKIP_MYSQL_TESTING) IS blank AND head_branch !~ /^core\//
- stage: test
name: 'plugins:cucumber (1/1) - postgres'
script:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -40,7 +40,6 @@
margin-bottom: 0.1875rem
padding: 0.375rem 0 !important
font-weight: bold
align-self: center
.attributes-key-value--value-container
@include grid-content(8)

@ -190,8 +190,14 @@ class ProjectsController < ApplicationController
end
def archive
flash[:error] = l(:error_can_not_archive_project) unless @project.archive
redirect_to(url_for(controller: '/projects', action: 'index', status: params[:status]))
projects_url = url_for(controller: '/projects', action: 'index', status: params[:status])
if @project.archive
redirect_to projects_url
else
flash[:error] = I18n.t(:error_can_not_archive_project)
redirect_back fallback_location: projects_url
end
update_demo_project_settings @project, false
end

@ -51,6 +51,9 @@ module Concerns
send(attribute)
super(*args)
end
# Ensure the virtual attribute is unset before destroying
before_destroy { send(:"#{attribute}=", nil) }
end
def _define_virtual_attribute_setter(attribute)

@ -64,9 +64,9 @@ class WorkPackages::UpdateAncestorsService
end
def update_former_ancestors(attributes)
return [] unless (%i(parent_id parent) & attributes).any? && previous_parent_id(work_package)
return [] unless (%i(parent_id parent) & attributes).any? && previous_parent_id
parent = WorkPackage.find(previous_parent_id(work_package))
parent = WorkPackage.find(previous_parent_id)
([parent] + parent.ancestors).each do |ancestor|
inherit_attributes(ancestor, %i(estimated_hours done_ratio))
@ -161,7 +161,20 @@ class WorkPackages::UpdateAncestorsService
work_packages.map(&:estimated_hours).reject { |hours| hours.to_f.zero? }
end
def previous_parent_id(work_package)
##
# Get the previous parent ID
# This could either be +parent_id_was+ if parent was changed
# (when work_package was saved/destroyed)
# Or the set parent before saving
def previous_parent_id
if work_package.parent_id.nil? && work_package.parent_id_was
work_package.parent_id_was
else
previous_change_parent_id
end
end
def previous_change_parent_id
previous = work_package.previous_changes
previous_parent_changes = (previous[:parent_id] || previous[:parent])

@ -273,8 +273,8 @@ ru:
label_attachments: Файлы
label_drop_files: Перетащите файлы сюда
label_drop_files_hint: или кликните для добавления файлов
label_drop_folders_hint: You cannot upload folders as an attachment. Please select
single files.
label_drop_folders_hint: Вы не можете загрузить папку. Выберите пожалуйста один
файл.
label_add_attachments: Добавить вложения
label_formattable_attachment_hint: Прикрепите и связать файлы, перетащив их на
это поле, или вставив из буфера обмена.
@ -415,7 +415,7 @@ ru:
hierarchy_headline: Иерархии
children_headline: Дочерние элементы
relation_buttons:
set_parent: Set parent
set_parent: Установить родителя
change_parent: Сменить родителя
remove_parent: Удалить родителя
group_by_wp_type: Группировать по типу комплекса работ

@ -185,8 +185,8 @@ ru:
projects: Проекты
enabled_projects: Доступные проекты
add_group: Добавить группу атрибутов
add_table: Add table of related work packages
edit_query: Edit table
add_table: Добавить таблицу связанных пакетов работ
edit_query: Изменить таблицу
query_group_placeholder: Give the table a name
reset: Восстановить значения по умолчанию
type_color_text: 'Щелкните, чтобы задать или изменить цвет этого типа. Выбранный

@ -1,7 +1,5 @@
import {ProjectResource} from 'core-app/modules/hal/resources/project-resource';
import {QueryFormResource} from 'core-app/modules/hal/resources/query-form-resource';
import {QueryGroupByResource} from 'core-app/modules/hal/resources/query-group-by-resource';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {QuerySortByResource} from 'core-app/modules/hal/resources/query-sort-by-resource';
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';

@ -10,7 +10,7 @@
(execute)="open()"
*ngIf="canModifyParent()"
[linkTitle]="parent ? text.edit_parent : text.set_parent"
linkClass="wp-relation--parent-change">
linkClass="wp-relation--parent-change hide-when-print">
<op-icon icon-classes="icon-small {{ parent ? 'icon-edit icon2' : 'icon-add icon4' }}"></op-icon>
<span *ngIf="!parent" [textContent]="text.set_parent"></span>
</accessible-by-keyboard>

@ -203,6 +203,12 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
return this.hook.call('attributeGroupComponent', group, this.workPackage).pop() || null;
}
public attachmentUploadComponent() {
// we take the last registered group component which means that
// plugins will have their say if they register for it.
return this.hook.call('workPackageAttachmentUploadComponent', this.workPackage).pop() || null;
}
/*
* Returns the work package label
*/

@ -119,6 +119,8 @@
</div>
<attachment-list [resource]="workPackage" [selfDestroy]="true"></attachment-list>
<attachments-upload [resource]="workPackage" class="hide-when-print"></attachments-upload>
<ndc-dynamic [ndcDynamicComponent]="attachmentUploadComponent()"
[ndcDynamicInputs]="{ resource: workPackage }">
</ndc-dynamic>
</div>
</div>

@ -93,7 +93,7 @@ export class WorkPackageEditFieldComponent implements OnInit {
// Open the field when its closed and relay drag & drop events to it.
public startDragOverActivation(event:JQueryEventObject) {
if (!this.isDropTarget || this.active) {
if (!this.isDropTarget || !this.isEditable || this.active) {
return true;
}

@ -144,7 +144,7 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
.subscribe((orderedRows) => {
// Remember all visible rows in their order of appearance.
this.workPackageIdOrder = orderedRows.filter(row => !row.hidden);
this.refreshRequest.putValue(undefined);
this.refreshView();
});
// Refresh timeline view when becoming visible

@ -124,10 +124,10 @@ export class AttachmentsUploadComponent implements OnInit {
}
}
private uploadFiles(files:UploadFile[]):void {
protected uploadFiles(files:UploadFile[]):void {
files = files || [];
const countBefore = files.length;
files = this.filterFolders(files)
files = this.filterFolders(files);
if (files.length === 0) {
@ -147,7 +147,7 @@ export class AttachmentsUploadComponent implements OnInit {
* or empty file sizes.
* @param files
*/
private filterFolders(files:UploadFile[]) {
protected filterFolders(files:UploadFile[]) {
return files.filter((file) => {
// Folders never have a mime type

@ -46,6 +46,7 @@ import {AttachmentsUploadComponent} from "core-app/modules/attachments/attachmen
],
entryComponents: [
AttachmentsComponent,
AttachmentsUploadComponent
],
exports: [
AttachmentsUploadComponent,

@ -1,4 +1,4 @@
import {ApplicationRef, Injector} from "@angular/core";
import {ApplicationRef, Injector, NgZone} from "@angular/core";
import {HookService} from "core-app/modules/plugins/hook-service";
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service";
import {ConfirmDialogService} from "core-components/modals/confirm-dialog/confirm-dialog.service";
@ -76,6 +76,9 @@ export class OpenProjectPluginContext {
// Hooks
public readonly hooks:{ [hook:string]:(callback:Function) => void } = {};
// Angular zone reference
public readonly zone:NgZone = this.injector.get(NgZone);
// Angular2 global injector reference
constructor(public readonly injector:Injector) {
this
@ -85,6 +88,20 @@ export class OpenProjectPluginContext {
});
}
/**
* Run the given callback in the angular zone,
* resulting in triggered change detection that would otherwise not occur.
*
* @param cb
*/
public runInZone(cb:() => void) {
this.zone.run(cb);
}
/**
* Bootstrap a dynamically embeddable component
* @param element
*/
public bootstrap(element:HTMLElement) {
DynamicBootstrapper.bootstrapOptionalEmbeddable(
this.injector.get(ApplicationRef),

@ -184,6 +184,7 @@ import {WorkPackagesBaseComponent} from "core-app/modules/work_packages/routing/
import {WorkPackagesListComponent} from "core-app/modules/work_packages/routing/wp-list/wp-list.component";
import {WorkPackageSplitViewComponent} from "core-app/modules/work_packages/routing/wp-split-view/wp-split-view.component";
import {WorkPackagesFullViewComponent} from "core-app/modules/work_packages/routing/wp-full-view/wp-full-view.component";
import {AttachmentsUploadComponent} from 'core-app/modules/attachments/attachments-upload/attachments-upload.component';
@NgModule({
imports: [
@ -524,6 +525,10 @@ export class OpenprojectWorkPackagesModule {
return null;
}
});
hookService.register('workPackageAttachmentUploadComponent', (workPackage:WorkPackageResource) => {
return AttachmentsUploadComponent;
});
};
}
}

@ -25,7 +25,7 @@
<ul id="toolbar-items" class="toolbar-items hide-when-print">
<li class="toolbar-item hidden-for-mobile">
<wp-create-button
[allowed]="!!workPackage.addChild"
[allowed]="!!(workPackage.addChild || workPackage.$links.copy)"
stateName="work-packages.new">
</wp-create-button>
</li>

@ -101,13 +101,12 @@ module API
end
link :copy,
cache_if: -> { current_user_allowed_to(:move_work_packages, context: represented.project) } do
cache_if: -> { current_user_allowed_to(:add_work_packages, context: represented.project) } do
next if represented.new_record?
{
href: new_work_package_move_path(represented, copy: true, ids: [represented.id]),
type: 'text/html',
href: work_package_path(represented, 'copy'),
title: "Copy #{represented.subject}"
}
end

@ -38,9 +38,8 @@ run() {
eval $2;
}
run "mysql -u root -e \"CREATE DATABASE IF NOT EXISTS travis_ci_test DEFAULT CHARACTER SET = 'utf8' DEFAULT COLLATE 'utf8_general_ci';\""
run "mysql -u root -e \"GRANT ALL ON travis_ci_test.* TO 'travis'@'localhost';\""
run "cp script/templates/database.travis.mysql.yml config/database.yml"
run "psql -c 'create database travis_ci_test;' -U postgres"
run "cp script/templates/database.travis.postgres.yml config/database.yml"
run "bundle exec rake db:migrate"

@ -0,0 +1,52 @@
require 'spec_helper'
describe 'Milestones full screen v iew', js: true do
let(:type) { FactoryBot.create :type, is_milestone: true }
let(:project) { FactoryBot.create(:project, types: [type]) }
let!(:work_package) {
FactoryBot.create(:work_package,
project: project,
type: type,
subject: 'Foobar')
}
let(:wp_page) { ::Pages::FullWorkPackage.new(work_package, project) }
let(:button) { find('.add-work-package', wait: 5) }
before do
login_as(user)
wp_page.visit!
end
context 'user has :add_work_packages permission' do
let(:user) do
FactoryBot.create(:user, member_in_project: project, member_through_role: role)
end
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) do
%i[view_work_packages add_work_packages]
end
it 'shows the button as enabled' do
expect(button['disabled']).to be_falsey
button.click
expect(page).to have_selector('.menu-item', text: type.name)
end
end
context 'user has :view_work_packages permission only' do
let(:user) do
FactoryBot.create(:user, member_in_project: project, member_through_role: role)
end
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) do
%i[view_work_packages]
end
it 'shows the button as correctly disabled' do
expect(button['disabled']).to be_truthy
end
end
end

@ -857,8 +857,8 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
describe 'copy' do
it_behaves_like 'has a titled action link' do
let(:link) { 'copy' }
let(:href) { new_work_package_move_path(work_package, copy: true, ids: [work_package.id]) }
let(:permission) { :move_work_packages }
let(:href) { work_package_path(work_package, 'copy') }
let(:permission) { :add_work_packages }
let(:title) { "Copy #{work_package.subject}" }
end
end

@ -0,0 +1,79 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe WorkPackages::DestroyService, 'integration', type: :model do
let(:user) do
FactoryBot.create(:user,
member_in_project: project,
member_through_role: role)
end
let(:role) do
FactoryBot.create(:role,
permissions: permissions)
end
let(:permissions) do
%i(delete_work_packages view_work_packages add_work_packages manage_subtasks)
end
let(:project) { FactoryBot.create(:project) }
describe 'deleting a child with estimated_hours set' do
let(:parent) { FactoryBot.create(:work_package, project: project) }
let(:child) do
FactoryBot.create(:work_package,
project: project,
parent: parent,
estimated_hours: 123)
end
let(:instance) do
described_class.new(user: user, work_package: child)
end
subject { instance.call }
before do
# Ensure estimated_hours is inherited
::WorkPackages::UpdateAncestorsService.new(user: user, work_package: child).call(%i[estimated_hours])
parent.reload
end
it 'updates the parent estimated_hours' do
expect(child.estimated_hours).to eq 123
expect(parent.estimated_hours).to eq 123
expect(subject).to be_success
parent.reload
expect(parent.estimated_hours).to eq(nil)
end
end
end
Loading…
Cancel
Save