[27607] Implement parent selection in wp breadcrumb, drop subject

https://community.openproject.com/wp/27607
pull/6809/head
Oliver Günther 6 years ago
parent 1728da9d1f
commit 039cb26b6e
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 4
      app/assets/stylesheets/layout/_breadcrumb.sass
  2. 3
      config/locales/js-en.yml
  3. 4
      frontend/src/app/angular4-modules.ts
  4. 95
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.component.ts
  5. 25
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.html
  6. 20
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb.component.ts
  7. 27
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb.html
  8. 2
      frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb.sass
  9. 5
      frontend/src/app/components/work-packages/wp-relations-count/wp-relations-count.component.ts
  10. 1
      frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.html
  11. 35
      frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts
  12. 6
      frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.html
  13. 11
      frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.template.html
  14. 93
      frontend/src/app/components/wp-relations/wp-relations-parent/wp-relations-parent.component.ts
  15. 87
      frontend/src/app/components/wp-relations/wp-relations-parent/wp-relations-parent.html
  16. 36
      spec/features/work_packages/details/relations/hierarchy_spec.rb
  17. 31
      spec/support/components/work_packages/relations.rb

@ -101,10 +101,6 @@ ul.breadcrumb
max-width: 420px
@include text-shortener
&:first-child
&:before
display: none
// This is ugly. However, this way we do not need to touch complicated
// toolbar-container positioning.
body.action-show .wp-breadcrumb

@ -396,9 +396,11 @@ en:
relations_hierarchy:
parent_headline: "Parent"
hierarchy_headline: "Hierarchy"
children_headline: "Children"
relation_buttons:
set_parent: "Set parent work package"
change_parent: "Change parent"
remove_parent: "Remove parent"
group_by_wp_type: "Group by work package type"
@ -419,6 +421,7 @@ en:
relations_autocomplete:
placeholder: "Enter the related work package id"
parent_placeholder: "Choose new parent, press enter to unset, escape to cancel."
repositories:
select_tag: 'Select tag'

@ -124,7 +124,6 @@ import {WorkPackageQuerySelectableTitleComponent} from 'core-components/wp-query
import {UrlParamsHelperService} from 'core-components/wp-query/url-params-helper';
import {WorkPackageRelationsHierarchyComponent} from 'core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive';
import {WorkPackageRelationsHierarchyService} from 'core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service';
import {WpRelationParentComponent} from 'core-components/wp-relations/wp-relations-parent/wp-relations-parent.component';
import {WorkPackageRelationsService} from 'core-components/wp-relations/wp-relations.service';
import {NewestActivityOnOverviewComponent} from 'core-components/wp-single-view-tabs/activity-panel/activity-on-overview.component';
import {WorkPackageActivityTabComponent} from 'core-components/wp-single-view-tabs/activity-panel/activity-tab.component';
@ -226,6 +225,7 @@ import {WorkPackagesCalendarComponent} from './components/routing/wp-calendar/wp
import {FullCalendarModule} from 'ng-fullcalendar';
import {WorkPackagesCalendarController} from "core-components/wp-calendar/wp-calendar.component";
import {WorkPackagesEmbeddedCalendarEntryComponent} from "core-components/wp-table/embedded/wp-embedded-calendar-entry.component";
import {WorkPackageBreadcrumbParentComponent} from './components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.component';
@NgModule({
imports: [
@ -386,6 +386,7 @@ import {WorkPackagesEmbeddedCalendarEntryComponent} from "core-components/wp-tab
WorkPackageRelationsCountComponent,
WorkPackageWatchersCountComponent,
WorkPackageBreadcrumbComponent,
WorkPackageBreadcrumbParentComponent,
WorkPackageEditFieldGroupComponent,
WorkPackageSplitViewToolbarComponent,
WorkPackageWatcherButtonComponent,
@ -421,7 +422,6 @@ import {WorkPackagesEmbeddedCalendarEntryComponent} from "core-components/wp-tab
WorkPackageRelationsCreateComponent,
WorkPackageRelationsHierarchyComponent,
WpRelationsAutocompleteComponent,
WpRelationParentComponent,
// Watchers tab
WorkPackageWatchersTabComponent,

@ -0,0 +1,95 @@
// -- 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 {Component, Input, EventEmitter, Output} from '@angular/core';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageRelationsHierarchyService} from 'core-app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service';
import {WorkPackageNotificationService} from 'core-app/components/wp-edit/wp-notification.service';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
@Component({
templateUrl: './wp-breadcrumb-parent.html',
selector: 'wp-breadcrumb-parent',
})
export class WorkPackageBreadcrumbParentComponent {
@Input('workPackage') workPackage:WorkPackageResource;
@Output('onSwitch') onSwitch = new EventEmitter<boolean>();
public isSaving = false;
public text = {
edit_parent: this.I18n.t('js.relation_buttons.change_parent'),
set_or_remove_parent: this.I18n.t('js.relations_autocomplete.parent_placeholder'),
set_parent: this.I18n.t('js.relation_buttons.set_parent'),
};
private editing:boolean;
public constructor(
protected readonly I18n:I18nService,
protected readonly wpRelationsHierarchy:WorkPackageRelationsHierarchyService,
protected readonly wpNotifications:WorkPackageNotificationService
) {
}
public canModifyParent():boolean {
return !!this.workPackage.changeParent;
}
public get parent() {
return this.workPackage && this.workPackage.parent;
}
public get active():boolean {
return this.editing;
}
public toggle():void {
this.editing = !this.editing;
this.onSwitch.emit(this.editing);
}
public updateParent(newParentId:string|null) {
this.toggle();
if (_.isNil(newParentId)) {
newParentId = null;
}
if (_.get(this.parent, 'id', null) === newParentId) {
return;
}
this.isSaving = true;
this.wpRelationsHierarchy.changeParent(this.workPackage, newParentId)
.catch((error:any) => {
this.wpNotifications.handleRawError(error, this.workPackage);
})
.then(() => this.isSaving = false); // Behaves as .finally()
}
}

@ -0,0 +1,25 @@
<ng-container *ngIf="!active">
<a *ngIf="parent"
[attr.title]="parent.name"
uiSref="work-packages.show.activity"
[uiParams]="{workPackageId: parent.id}"
class="wp-breadcrumb-parent breadcrumb-project-title nocut">
<span [textContent]="parent.name"></span>
</a>
<accessible-by-keyboard
(execute)="toggle()"
*ngIf="canModifyParent()"
[linkTitle]="parent ? text.edit_parent : text.set_parent"
linkClass="wp-relation--parent-change">
<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>
</ng-container>
<wp-relations-autocomplete-upgraded
*ngIf="active"
[inputPlaceholder]="text.set_or_remove_parent"
[workPackage]="workPackage"
(onEscape)="this.toggle()"
(onWorkPackageIdSelected)="updateParent($event)"
filterCandidatesFor="parent">
</wp-relations-autocomplete-upgraded>

@ -28,13 +28,33 @@
import {Component, Input} from '@angular/core';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
@Component({
templateUrl: './wp-breadcrumb.html',
styleUrls: ['./wp-breadcrumb.sass'],
selector: 'wp-breadcrumb',
})
export class WorkPackageBreadcrumbComponent {
@Input('workPackage') workPackage:WorkPackageResource;
public text = {
parent: this.I18n.t('js.relations_hierarchy.parent_headline'),
hierarchy: this.I18n.t('js.relations_hierarchy.hierarchy_headline'),
};
constructor(private I18n:I18nService) {
}
public inputActive:boolean = false;
public get hierarchyCount() {
return this.workPackage.ancestors.length;
}
public get hierarchyLabel() {
return (this.hierarchyCount === 1) ? this.text.parent : this.text.hierarchy;
}
}

@ -1,15 +1,22 @@
<div class="wp-breadcrumb -show"
*ngIf="workPackage && workPackage.ancestors.length > 0">
*ngIf="workPackage">
<ul class="breadcrumb">
<li *ngFor="let ancestor of workPackage.ancestors"
class="icon4 icon-small icon-arrow-right5">
<a [attr.title]="ancestor.name"
[textContent]="ancestor.name"
uiSref="work-packages.show.activity"
[uiParams]="{workPackageId: ancestor.id}"
class="breadcrumb-project-title nocut"></a>
<ng-container *ngIf="!inputActive && hierarchyCount > 0">
<li>
<span>{{ hierarchyLabel }}: </span>
</li>
<li *ngFor="let ancestor of workPackage.ancestors; let first = first ; let last = last"
[ngClass]="{ 'icon4 icon-small icon-arrow-right5': !first }">
<a *ngIf="!last"
[attr.title]="ancestor.name"
[textContent]="ancestor.name"
uiSref="work-packages.show.activity"
[uiParams]="{workPackageId: ancestor.id}"
class="breadcrumb-project-title nocut"></a>
</li>
</ng-container>
<li [ngClass]="{ 'active-parent-select': inputActive }">
<wp-breadcrumb-parent (onSwitch)="inputActive = !inputActive" [workPackage]="workPackage"></wp-breadcrumb-parent>
</li>
<li class="icon4 icon-small icon-arrow-right5"
[textContent]="workPackage.name"></li>
</ul>
</div>

@ -1,7 +1,7 @@
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {RelationsStateValue, WorkPackageRelationsService} from '../../wp-relations/wp-relations.service';
import {WorkPackageRelationsService} from '../../wp-relations/wp-relations.service';
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service';
import {combineLatest} from 'rxjs';
@ -27,10 +27,9 @@ export class WorkPackageRelationsCountComponent implements OnInit, OnDestroy {
takeUntil(componentDestroyed(this))
).subscribe(([relations, workPackage]) => {
let relationCount = _.size(relations);
let parentCount = workPackage.parent ? 1 : 0;
let childrenCount = _.size(workPackage.children);
this.count = relationCount + parentCount + childrenCount;
this.count = relationCount + childrenCount;
});
}

@ -4,6 +4,7 @@
<div class="grid-content medium-10">
<wp-relations-autocomplete-upgraded
[workPackage]="workPackage"
appendToContainer=".work-packages--details-content"
(onWorkPackageIdSelected)="updateSelectedId($event)"
loadingPromiseName="relationAddChild"
filterCandidatesFor="children">

@ -38,16 +38,20 @@ import {CollectionResource} from 'core-app/modules/hal/resources/collection-reso
templateUrl: './wp-relations-autocomplete.upgraded.html'
})
export class WpRelationsAutocompleteComponent implements OnInit {
readonly text = {
placeholder: this.I18n.t('js.relations_autocomplete.placeholder')
};
@Input() workPackage:WorkPackageResource;
@Input() loadingPromiseName:string;
@Input() selectedRelationType:string;
@Input() filterCandidatesFor:string;
@Input() initialSelection?:WorkPackageResource;
@Input() inputPlaceholder:string = this.text.placeholder;
@Input() appendToContainer:string = '#content';
@Output('onWorkPackageIdSelected') public onSelect = new EventEmitter<string>();
readonly text = {
placeholder: this.I18n.t('js.relations_autocomplete.placeholder')
};
@Output('onWorkPackageIdSelected') public onSelect = new EventEmitter<string|null>();
@Output('onEscape') public onEscapePressed = new EventEmitter<KeyboardEvent>();
public options:any = [];
public relatedWps:any = [];
@ -67,16 +71,25 @@ export class WpRelationsAutocompleteComponent implements OnInit {
let input = this.$element.find('.wp-relations--autocomplete');
let selected = false;
if (this.initialSelection) {
input.val(this.getIdentifier(this.initialSelection));
}
input.autocomplete({
delay: 250,
autoFocus: false, // Accessibility!
appendTo: '#content',
appendTo: this.appendToContainer,
classes: {
'ui-autocomplete': 'wp-relations-autocomplete--results'
},
source: (request:{ term:string }, response:Function) => {
this.autocompleteWorkPackages(request.term).then((values) => {
selected = false;
if (this.initialSelection) {
values.unshift(this.initialSelection);
}
response(values.map(wp => {
return {workPackage: wp, value: this.getIdentifier(wp)};
}));
@ -87,11 +100,19 @@ export class WpRelationsAutocompleteComponent implements OnInit {
this.onSelect.emit(ui.item.workPackage.id);
},
minLength: 0
}).focus(() => !selected && input.autocomplete('search', input.val()));
})
.focus(() => !selected && input.autocomplete('search', input.val()));
setTimeout(() => input.focus(), 20);
}
public handleEnterPressed($event:KeyboardEvent) {
const val = ($event.target as HTMLInputElement).value;
if (!val) {
this.onSelect.emit(null);
}
}
private getIdentifier(workPackage:WorkPackageResource):string {
if (workPackage) {
return `#${workPackage.id} - ${workPackage.subject}`;

@ -1,8 +1,10 @@
<form name="add_relation_form" class="form">
<input type="text"
class="wp-relations--autocomplete ui-autocomplete--input"
[ngClass]="{ '-error': noResults }"
[attr.placeholder]="text.placeholder">
[ngClass]="{ '-error': noResults && !initialSelection }"
(keydown.enter)="handleEnterPressed($event)"
(keydown.escape)="onEscapePressed.emit($event)"
[attr.placeholder]="inputPlaceholder">
<div class="ui-autocomplete--loading" style="display: none">
<div class="loading-indicator -small">
<div class="block-1"></div>

@ -1,14 +1,3 @@
<div class="wp-relations-hierarchy-section">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text"
[textContent]="text.parentHeadline">
</h3>
</div>
</div>
<wp-relation-parent [workPackage]="workPackage"></wp-relation-parent>
</div>
<div class="wp-relations-hierarchy-section wp-relations--children">
<div class="attributes-group--header">
<div class="attributes-group--header-container">

@ -1,93 +0,0 @@
import {Component, ElementRef, Inject, Input, OnDestroy, OnInit} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service';
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service';
import {WorkPackageRelationsHierarchyService} from '../wp-relations-hierarchy/wp-relations-hierarchy.service';
@Component({
selector: 'wp-relation-parent',
templateUrl: './wp-relations-parent.html'
})
export class WpRelationParentComponent implements OnInit, OnDestroy {
@Input() public workPackage:WorkPackageResource;
public showEditForm:boolean = false;
public canModifyHierarchy:boolean = false;
public selectedWpId:string | null = null;
public isSaving = false;
constructor(readonly elementRef:ElementRef,
readonly wpRelationsHierarchyService:WorkPackageRelationsHierarchyService,
readonly wpCacheService:WorkPackageCacheService,
readonly wpNotificationsService:WorkPackageNotificationService,
readonly PathHelper:PathHelperService,
readonly I18n:I18nService) {
}
public text = {
add_parent: this.I18n.t('js.relation_buttons.add_parent'),
change_parent: this.I18n.t('js.relation_buttons.change_parent'),
remove_parent: this.I18n.t('js.relation_buttons.remove_parent'),
remove: this.I18n.t('js.relation_buttons.remove'),
parent: this.I18n.t('js.relation_labels.parent'),
abort: this.I18n.t('js.relation_buttons.abort'),
save: this.I18n.t('js.relation_buttons.save'),
};
ngOnDestroy() {
// Nothing to do
}
ngOnInit() {
this.canModifyHierarchy = !!this.workPackage.changeParent;
this.wpCacheService.state(this.workPackage.id)
.values$()
.pipe(
takeUntil(componentDestroyed(this))
)
.subscribe(wp => this.workPackage = wp);
}
public updateSelectedId(workPackageId:string) {
this.selectedWpId = workPackageId;
}
public changeParent() {
if (_.isNil(this.selectedWpId)) {
return;
}
const newParentId = this.selectedWpId;
this.showEditForm = false;
this.selectedWpId = null;
this.isSaving = true;
this.wpRelationsHierarchyService.changeParent(this.workPackage, newParentId)
.then((updatedWp:WorkPackageResource) => {
setTimeout(() => jQuery('#hierarchy--parent').focus());
})
.catch((err:any) => {
this.wpNotificationsService.handleRawError(err, this.workPackage);
})
.then(() => this.isSaving = false); // Behaves as .finally()
}
public get relationReady() {
return this.workPackage.parent && this.workPackage.parent.$loaded;
}
public removeParent() {
this.wpRelationsHierarchyService
.removeParent(this.workPackage)
.then(() => {
this.wpNotificationsService.showSave(this.workPackage);
setTimeout(() => {
jQuery('#hierarchy--add-parent').focus();
});
});
}
}

@ -1,87 +0,0 @@
<div class="relation-row relation-row--parent"
focus-within>
<div class="grid-block v-align hierarchy-item"
*ngIf="!showEditForm && relationReady">
<div class="grid-content medium-7 collapse" wp-single-relation>
<span ng-style="{'padding-left': indentBy + 'px'}"
class="wp-relations-hierarchy-subject">
<a uiSref="work-packages.show.relations"
[uiParams]="{ workPackageId: workPackage.parent.id }"
class="wp-relations--subject-field"
id="wp-relations-parent-element"
[attr.aria-label]="text.parent"
[textContent]="workPackage.parent.subjectWithType()">
</a>
</span>
</div>
<div class="grid-content medium-3 collapse wp-relations-status-field">
<wp-edit-field-group [workPackage]="workPackage.parent">
<div *ngIf="relationReady">
<wp-edit-field [workPackageId]="workPackage.parent.id"
fieldName="status">
</wp-edit-field>
</div>
</wp-edit-field-group>
</div>
<div class="grid-content medium-2 collapse wp-relations-controls-section focus-within--depending">
<accessible-by-keyboard *ngIf="canModifyHierarchy"
(execute)="showEditForm = true"
linkClass="wp-relation--edit">
<op-icon icon-classes="icon-edit -padded" [icon-title]="text.change_parent"></op-icon>
</accessible-by-keyboard>
<accessible-by-keyboard *ngIf="canModifyHierarchy"
(execute)="removeParent()"
linkClass="wp-relation--remove">
<op-icon icon-classes="icon-remove -padded"
[icon-title]="text.remove_parent"></op-icon>
</accessible-by-keyboard>
</div>
</div>
<div *ngIf="canModifyHierarchy && (!workPackage.parent || showEditForm)" class="hide-when-print">
<div class="wp-relations-create-button -full-width"
*ngIf="!showEditForm">
<div class="grid-block">
<div class="grid-content collapse hide-when-print wp-inline-create-button">
<a class="wp-inline-create--add-link relation-create"
(click)="showEditForm = true"
role="button"
id="hierarchy--add-parent">
<op-icon icon-classes="icon icon-add"></op-icon>
<span [textContent]="text.add_parent"></span>
</a>
</div>
</div>
</div>
<div class="loading-indicator--location"
*ngIf="showEditForm"
data-indicator-name="relationAddParent">
<div class="v-align wp-relations-create--form wp-relations--parent-form">
<div class="grid-content medium-10">
<wp-relations-autocomplete-upgraded
[workPackage]="workPackage"
(onWorkPackageIdSelected)="updateSelectedId($event)"
loadingPromiseName="relationAddParent"
filterCandidatesFor="parent">
</wp-relations-autocomplete-upgraded>
</div>
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row">
<accessible-by-keyboard
linkClass="wp-create-relation--save"
[isDisabled]="!selectedWpId || isSaving"
(execute)="changeParent()"
aria-hidden="false">
<op-icon icon-classes="icon-checkmark -padded" [icon-title]="text.save"></op-icon>
</accessible-by-keyboard>
<accessible-by-keyboard
linkClass="wp-create-relation--cancel"
(execute)="showEditForm = false"
aria-hidden="false">
<op-icon icon-classes="icon-remove -padded" [icon-title]="text.abort"></op-icon>
</accessible-by-keyboard>
</div>
</div>
</div>
</div>
</div>

@ -35,11 +35,6 @@ shared_examples 'work package relations tab', js: true, selenium: true do
let!(:child2) { FactoryBot.create(:work_package, project: project, subject: 'Another child WP') }
it 'allows to manage hierarchy' do
# Shows link parent link
expect(page).to have_selector('#hierarchy--add-parent')
find('.wp-inline-create--add-link',
text: I18n.t('js.relation_buttons.add_parent')).click
# Add parent
relations.add_parent(parent.id, parent)
relations.expect_parent(parent)
@ -58,8 +53,8 @@ shared_examples 'work package relations tab', js: true, selenium: true do
relations.add_existing_child(child2)
# Count parent and child relations in split view
tabs.expect_counter(relations_tab, 3)
# Count child relations in split view
tabs.expect_counter(relations_tab, 2)
end
describe 'inline create' do
@ -129,8 +124,8 @@ shared_examples 'work package relations tab', js: true, selenium: true do
end
context 'as view-only user, with parent set' do
let(:parent) { FactoryBot.create(:work_package, project: project, subject: 'Parent WP') }
let(:work_package) { FactoryBot.create(:work_package, parent: parent, project: project, subject: 'Child WP') }
let!(:parent) { FactoryBot.create(:work_package, project: project, subject: 'Parent WP') }
let!(:work_package) { FactoryBot.create(:work_package, parent: parent, project: project, subject: 'Child WP') }
it 'shows no links to create relations' do
# No create buttons should exist
@ -140,17 +135,17 @@ shared_examples 'work package relations tab', js: true, selenium: true do
expect(page).to have_no_selector('#relation--add-relation')
# Test for add parent
expect(page).to have_no_selector('#hierarchy--add-parent')
expect(page).to have_no_selector('.wp-relation--parent-change')
# Test for add children
expect(page).to have_no_selector('#hierarchy--add-exisiting-child')
expect(page).to have_no_selector('#hierarchy--add-new-child')
# But it should show the linked parent
expect(page).to have_selector('.wp-relations-hierarchy-subject', text: parent.subject)
expect(page).to have_selector('.wp-breadcrumb-parent', text: parent.subject)
# And it should count parent and the two relations
tabs.expect_counter(relations_tab, 3)
# And it should count the two relations
tabs.expect_counter(relations_tab, 2)
end
end
@ -160,11 +155,6 @@ shared_examples 'work package relations tab', js: true, selenium: true do
let!(:child) { FactoryBot.create(:work_package, project: project, subject: 'Child WP') }
it 'should be able to link parent and children' do
# Shows link parent link
expect(page).to have_selector('#hierarchy--add-parent')
find('.wp-inline-create--add-link',
text: I18n.t('js.relation_buttons.add_parent')).click
# Add parent
relations.add_parent(parent.id, parent)
wp_page.expect_and_dismiss_notification(message: 'Successful update.')
@ -179,20 +169,20 @@ shared_examples 'work package relations tab', js: true, selenium: true do
wp_page.expect_and_dismiss_notification(message: 'Successful update.')
relations.expect_child(child)
# Expect counter to add up new parent and child to the existing relations
tabs.expect_counter(relations_tab, 4)
# Expect counter to add up child to the existing relations
tabs.expect_counter(relations_tab, 3)
# Remove parent
relations.remove_parent(parent)
relations.remove_parent
wp_page.expect_and_dismiss_notification(message: 'Successful update.')
relations.expect_not_parent(parent)
relations.expect_no_parent
# Remove child
relations.remove_child(child)
# Should also check for successful update but no message is shown, yet.
relations.expect_not_child(child)
# Expect counter to only count the two existing relations
# Expect counter to count the two relations
tabs.expect_counter(relations_tab, 2)
end
end

@ -124,35 +124,36 @@ module Components
end
def add_parent(query, work_package)
# Locate the create row container
container = find('.wp-relations--parent-form')
# Open the parent edit
find('.wp-relation--parent-change').click
# Enter the query and select the child
autocomplete = container.find(".wp-relations--autocomplete")
autocomplete = find(".wp-relations--autocomplete")
select_autocomplete autocomplete,
query: query,
results_selector: '.wp-relations-autocomplete--results',
select_text: work_package.id
container.find('.wp-create-relation--save').click
end
def expect_parent(work_package)
expect(page).to have_selector('.wp-relations-hierarchy-subject',
expect(page).to have_selector '.wp-breadcrumb-parent',
text: work_package.subject,
wait: 10)
wait: 10
end
def remove_parent(work_package)
expect(page).to have_selector('.relation-row--parent', text: work_package.subject)
container = find('.relation-row--parent')
container.hover
container.find('.wp-relation--remove').click
def expect_no_parent
expect(page).to have_no_selector '.wp-breadcrumb-parent', wait: 10
end
def expect_not_parent(work_package)
expect(page).to have_no_selector('.relation-row--parent', text: work_package.subject, wait: 10)
def remove_parent
# Open the parent edit
find('.wp-relation--parent-change').click
# Submit empty autocomplete to remove
autocomplete = find(".wp-relations--autocomplete")
autocomplete.set ''
autocomplete.send_keys :enter
end
def inline_create_child(subject_text)

Loading…
Cancel
Save