Merge pull request #6973 from opf/fix/schema_update_in_inline_create

Fix/schema update in inline create

[ci skip]
pull/6987/head
Oliver Günther 6 years ago committed by GitHub
commit f7515bb25a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      frontend/src/app/components/wp-copy/wp-copy.controller.ts
  2. 72
      frontend/src/app/components/wp-edit-form/work-package-changeset.ts
  3. 80
      frontend/src/app/components/wp-edit-form/work-package-filter-values.ts
  4. 7
      frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts
  5. 100
      frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts
  6. 44
      frontend/src/app/components/wp-new/wp-create.controller.ts
  7. 100
      frontend/src/app/components/wp-new/wp-create.service.ts
  8. 3
      frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts
  9. 4
      frontend/src/app/modules/fields/edit/edit-field.component.ts
  10. 3
      spec/features/work_packages/new/new_work_package_spec.rb
  11. 84
      spec/features/work_packages/table/inline_create/create_work_packages_spec.rb
  12. 6
      spec/features/work_packages/table/switch_types_spec.rb
  13. 1
      spec/support/components/work_packages/columns.rb
  14. 1
      spec/support/components/work_packages/relations.rb

@ -32,12 +32,15 @@ import {WorkPackageChangeset} from 'core-components/wp-edit-form/work-package-ch
import {WorkPackageCreateController} from 'core-components/wp-new/wp-create.controller';
import {WorkPackageRelationsService} from "core-components/wp-relations/wp-relations.service";
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed";
import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service";
import {IWorkPackageEditingServiceToken} from "core-components/wp-edit-form/work-package-editing.service.interface";
export class WorkPackageCopyController extends WorkPackageCreateController {
private __initialized_at:Number;
private copiedWorkPackageId:string;
private wpRelations:WorkPackageRelationsService = this.injector.get(WorkPackageRelationsService);
protected wpEditing:WorkPackageEditingService = this.injector.get<WorkPackageEditingService>(IWorkPackageEditingServiceToken);
ngOnInit() {
super.ngOnInit();
@ -53,8 +56,8 @@ export class WorkPackageCopyController extends WorkPackageCreateController {
});
}
protected newWorkPackageFromParams(stateParams:any) {
this.copiedWorkPackageId = stateParams.copiedFromWorkPackageId;
protected createdWorkPackage() {
this.copiedWorkPackageId = this.stateParams.copiedFromWorkPackageId;
return new Promise<WorkPackageChangeset>((resolve, reject) => {
this.wpCacheService.loadWorkPackage(this.copiedWorkPackageId)
.values$()
@ -80,6 +83,10 @@ export class WorkPackageCopyController extends WorkPackageCreateController {
.copyWorkPackage(form, wp.project.identifier)
.then((changeset) => {
this.__initialized_at = changeset.workPackage.__initialized_at;
this.wpCacheService.updateWorkPackage(changeset.workPackage);
this.wpEditing.updateValue('new', changeset);
return changeset;
})
);

@ -26,7 +26,6 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {input, InputState} from 'reactivestates';
import {debugLog} from '../../helpers/debug_output';
import {SchemaCacheService} from '../schemas/schema-cache.service';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
@ -46,7 +45,7 @@ import {
} from "core-components/wp-edit-form/work-package-editing.service.interface";
import {HalResource} from "core-app/modules/hal/resources/hal-resource";
import {IFieldSchema} from "core-app/modules/fields/field.base";
import {take} from "rxjs/operators";
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
export class WorkPackageChangeset {
// Injections
@ -62,10 +61,8 @@ export class WorkPackageChangeset {
private changes:{ [attribute:string]:any } = {};
public inFlight:boolean = false;
// The current work package form
public wpForm:FormResource|undefined;
public wpFormPromise:Promise<FormResource>|undefined;
private wpFormPromise:Promise<FormResource>|null;
public form:FormResource|null;
// The current editing resource
public resource:WorkPackageResource|null;
@ -73,10 +70,7 @@ export class WorkPackageChangeset {
constructor(readonly injector:Injector,
public workPackage:WorkPackageResource,
form?:FormResource) {
// New work packages have no schema set yet, so update the form immediately to get one
if (form !== undefined) {
this.wpForm = form;
}
this.form = form || null;
}
public reset(key:string) {
@ -90,8 +84,7 @@ export class WorkPackageChangeset {
}
public resetForm() {
this.wpForm = undefined;
this.wpFormPromise = undefined;
this.form = null;
}
public get empty() {
@ -141,12 +134,11 @@ export class WorkPackageChangeset {
}
public getForm():Promise<FormResource> {
if (this.wpForm) {
return Promise.resolve(this.wpForm)
if (!this.form) {
return this.updateForm();
} else {
return Promise.resolve(this.form);
}
this.wpFormPromise = this.wpFormPromise || this.updateForm();
return this.wpFormPromise;
}
/**
@ -155,18 +147,26 @@ export class WorkPackageChangeset {
public updateForm():Promise<FormResource> {
let payload = this.buildPayloadFromChanges();
return this.workPackage.$links
.update(payload)
.then((form:FormResource) => {
this.wpForm = form;
this.buildResource();
return form;
})
.catch((error:any) => {
this.resetForm();
throw error;
});
if (!this.wpFormPromise) {
this.wpFormPromise = this.workPackage.$links
.update(payload)
.then((form:FormResource) => {
this.form = form;
this.buildResource();
this.wpFormPromise = null;
return form;
})
.catch((error:any) => {
this.resetForm();
this.wpFormPromise = null;
throw error;
});
}
return this.wpFormPromise;
}
public save():Promise<WorkPackageResource> {
@ -235,8 +235,8 @@ export class WorkPackageChangeset {
private mergeWithPayload(plainPayload:any) {
// Fall back to the last known state of the work package should the form not be loaded.
let reference = this.workPackage.$source;
if (this.wpForm) {
reference = this.wpForm.payload.$source;
if (this.form) {
reference = this.form.payload.$source;
}
_.each(this.changes, (val:any, key:string) => {
@ -267,8 +267,8 @@ export class WorkPackageChangeset {
if (this.workPackage.isNew) {
// If the work package is new, we need to pass the entire form payload
// to let all default values be transmitted (type, status, etc.)
if (this.wpForm) {
payload = this.wpForm.payload.$source;
if (this.form) {
payload = this.form.payload.$source;
} else {
payload = this.workPackage.$source;
}
@ -335,8 +335,8 @@ export class WorkPackageChangeset {
* If loaded, return the form schema, which provides better information on writable status
* and contains available values.
*/
public get schema():HalResource {
return (this.wpForm || this.workPackage).schema;
public get schema():SchemaResource {
return (this.form || this.workPackage).schema;
}
public getSchemaName(attribute:string):string {
@ -354,12 +354,10 @@ export class WorkPackageChangeset {
return;
}
const hasForm = !!this.wpForm;
let payload:any = this.workPackage.$source;
const resource = this.halResourceService.createHalResourceOfType('WorkPackage', this.mergeWithPayload(payload));
// Override the schema with the current form, if any.
resource.overriddenSchema = this.schema;
this.resource = (resource as WorkPackageResource);
this.wpEditing.updateValue(this.workPackage.id, this);

@ -20,43 +20,34 @@ export class WorkPackageFilterValues {
}
public applyDefaultsFromFilters() {
return this.changeset.getForm().then((form) => {
const promises:Promise<any>[] = [];
_.each(this.filters, filter => {
// Ignore any filters except =
if (filter.operator.id !== '=') {
return;
}
// Exclude filters specified in constructor
if (this.excluded.indexOf(filter.id) !== -1) {
return;
}
// Select the first value
var value = filter.values[0];
_.each(this.filters, filter => {
// Ignore any filters except =
if (filter.operator.id !== '=') {
return;
}
// Avoid empty values
if (!value) {
return;
}
// Exclude filters specified in constructor
if (this.excluded.indexOf(filter.id) !== -1) {
return;
}
promises.push(this.setAllowedValueFor(form, filter.id, value));
});
// Select the first value
let value = filter.values[0];
return Promise.all(promises);
// Avoid empty values
if (value) {
this.setValueFor(filter.id, value);
}
});
}
private setAllowedValueFor(form:FormResource, field:string, value:string|HalResource) {
return this.allowedValuesFor(form, field).then((allowedValues) => {
let newValue = this.findSpecialValue(value, field) || this.findAllowedValue(value, allowedValues);
private setValueFor(field:string, value:string|HalResource) {
let newValue = this.findSpecialValue(value, field) || value;
if (newValue) {
this.changeset.setValue(field, newValue);
this.changeset.workPackage[field] = newValue;
}
});
if (newValue) {
this.changeset.setValue(field, newValue);
this.changeset.workPackage[field] = newValue;
}
}
/**
@ -75,33 +66,4 @@ export class WorkPackageFilterValues {
return undefined;
}
private findAllowedValue(value:string|HalResource, allowedValues:HalResource[]) {
if (value instanceof HalResource && !!value.$href) {
return _.find(allowedValues,
(entry:any) => entry.$href === value.$href);
} else if (allowedValues) {
return _.find(allowedValues, (entry:any) => entry === value);
} else {
return value;
}
}
private allowedValuesFor(form:FormResource, field:string):Promise<HalResource[]> {
const fieldSchema = form.schema[field];
return new Promise<HalResource[]>(resolve => {
if (!fieldSchema) {
resolve([]);
} else if (fieldSchema.allowedValues && fieldSchema.allowedValues['$load']) {
let allowedValues = fieldSchema.allowedValues;
return allowedValues.$load().then((loadedValues:CollectionResource) => {
resolve(loadedValues.elements);
});
} else {
resolve(fieldSchema.allowedValues);
}
});
}
}

@ -3,7 +3,6 @@ import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {locateTableRowByIdentifier} from 'core-components/wp-fast-table/helpers/wp-table-row-helpers';
import {debugLog} from '../../../../helpers/debug_output';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageChangeset} from '../../../wp-edit-form/work-package-changeset';
import {isRelationColumn, QueryColumn} from '../../../wp-query/query-column';
import {WorkPackageTableColumnsService} from '../../state/wp-table-columns.service';
import {WorkPackageTableSelection} from '../../state/wp-table-selection.service';
@ -142,12 +141,8 @@ export class SingleRowBuilder {
protected isColumnBeingEdited(workPackage:WorkPackageResource, column:QueryColumn) {
const form = this.workPackageTable.editing.forms[workPackage.id];
const changeset = this.workPackageTable.editing.changeset(workPackage.id);
const isOpen = form && form.activeFields[column.id];
const isChanged = changeset && changeset.isOverridden(column.id);
return isOpen || isChanged;
return form && form.activeFields[column.id];
}
protected buildEmptyRow(workPackage:WorkPackageResource, row:HTMLElement):[HTMLElement, boolean] {

@ -41,12 +41,8 @@ import {AuthorisationService} from 'core-app/modules/common/model-auth/model-aut
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {filter} from 'rxjs/operators';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context';
import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset';
import {WorkPackageEditForm} from '../wp-edit-form/work-package-edit-form';
import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service';
import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values';
import {TimelineRowBuilder} from '../wp-fast-table/builders/timeline/timeline-row-builder';
import {onClickOrEnter} from '../wp-fast-table/handlers/click-or-enter-handler';
import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service';
@ -61,10 +57,10 @@ import {TableState} from 'core-components/wp-table/table-state/table-state';
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {FocusHelperService} from 'core-app/modules/common/focus/focus-helper';
import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-editing.service.interface";
import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface";
import {CurrentUserService} from "core-components/user/current-user.service";
import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service";
import {Subscription} from 'rxjs';
@Component({
selector: '[wpInlineCreate]',
@ -91,6 +87,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
private rowBuilder:InlineCreateRowBuilder;
private timelineBuilder:TimelineRowBuilder;
private editingSubscription:Subscription|undefined;
private $element:JQuery;
@ -99,9 +96,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
protected readonly FocusHelper:FocusHelperService,
protected readonly I18n:I18nService,
protected readonly tableState:TableState,
protected readonly wpCacheService:WorkPackageCacheService,
protected readonly currentUser:CurrentUserService,
@Inject(IWorkPackageEditingServiceToken) protected readonly wpEditing:WorkPackageEditingService,
@Inject(IWorkPackageCreateServiceToken) protected readonly wpCreate:WorkPackageCreateService,
protected readonly wpInlineCreate:WorkPackageInlineCreateService,
protected readonly wpTableColumns:WorkPackageTableColumnsService,
@ -169,13 +164,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
filter(() => this.isActive), // Take only when row is inserted
untilComponentDestroyed(this),
)
.subscribe(() => {
const rowElement = this.$element.find(`.${inlineCreateRowClassName}`);
if (rowElement.length && this.currentWorkPackage) {
this.rowBuilder.refreshRow(this.currentWorkPackage, rowElement);
}
});
.subscribe(() => this.refreshRow());
}
/**
@ -200,7 +189,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id);
} else {
// Remove current row
this.table.editing.stopEditing('new');
this.wpCreate.cancelCreation();
this.removeWorkPackageRow();
this.showRow();
}
@ -227,49 +216,46 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
public addWorkPackageRow() {
this.wpCreate
.createNewWorkPackage(this.projectIdentifier)
.createOrContinueWorkPackage(this.projectIdentifier)
.then((changeset:WorkPackageChangeset) => {
if (!changeset) {
throw 'No new work package was created';
}
const wp = this.currentWorkPackage = changeset.workPackage;
this.applyDefaultsAndInsert(changeset, wp);
this.editingSubscription = this
.wpCreate
.changesetUpdates$()
.pipe(
filter((cs) => !!this.currentWorkPackage && !!cs.form),
).subscribe((form) => {
if (!this.isActive) {
this.insertRow(wp);
} else {
this.currentWorkPackage!.overriddenSchema = form!.schema;
this.refreshRow();
}
});
});
}
/**
* Apply values to the work package from the current set of filters
*
* @param changeset
* @param wp
*/
private applyDefaultsAndInsert(changeset:WorkPackageChangeset, wp:WorkPackageResource) {
let promise:Promise<any>;
if (this.tableState.query.hasValue()) {
const filter = new WorkPackageFilterValues(this.injector, changeset, this.tableState.query.value!.filters);
promise = filter.applyDefaultsFromFilters();
} else {
promise = Promise.resolve();
}
private insertRow(wp:WorkPackageResource) {
// Actually render the row
const form = this.workPackageEditForm = this.renderInlineCreateRow(wp);
promise.then(() => {
// Update the changeset with any added filtered values
this.wpEditing.updateValue('new', changeset);
this.wpCacheService.updateWorkPackage(this.currentWorkPackage!);
setTimeout(() => {
// Activate any required fields
form.activateMissingFields();
// Actually render the row
const form = this.workPackageEditForm = this.renderInlineCreateRow(wp);
// Hide the button row
this.hideRow();
});
}
setTimeout(() => {
// Activate any required fields
form.activateMissingFields();
private refreshRow() {
const rowElement = this.$element.find(`.${inlineCreateRowClassName}`);
// Hide the button row
this.hideRow();
});
});
if (rowElement.length && this.currentWorkPackage) {
this.rowBuilder.refreshRow(this.currentWorkPackage, rowElement);
}
}
/**
@ -280,16 +266,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
* @returns The work package form of the row
*/
private renderInlineCreateRow(wp:WorkPackageResource):WorkPackageEditForm {
// Set editing context to table
const context = new TableRowEditContext(
this.table,
this.injector,
wp.id,
this.rowBuilder.classIdentifier(wp)
);
const form = WorkPackageEditForm.createInContext(this.injector, context, wp, false);
form.changeset.clear();
const form = this.table.editing.startEditing(wp, this.rowBuilder.classIdentifier(wp));
const [row, ] = this.rowBuilder.buildNew(wp, form);
this.$element.append(row);
@ -311,10 +288,12 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
}
public removeWorkPackageRow() {
this.wpCreate.cancelCreation();
this.currentWorkPackage = null;
this.table.editing.stopEditing('new');
this.wpCacheService.clearSome('new');
this.$element.find('.wp-row-new').remove();
if (this.editingSubscription) {
this.editingSubscription.unsubscribe();
}
}
public showRow() {
@ -336,5 +315,4 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
public get canAdd():boolean {
return this.wpInlineCreate.canAdd;
}
}

@ -35,8 +35,6 @@ import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-r
import {RootResource} from 'core-app/modules/hal/resources/root-resource';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset';
import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service';
import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
import {WorkPackageTableFiltersService} from '../wp-fast-table/state/wp-table-filters.service';
import {WorkPackageCreateService} from './wp-create.service';
@ -44,9 +42,6 @@ import {takeUntil} from 'rxjs/operators';
import {RootDmService} from 'core-app/modules/hal/dm-services/root-dm.service';
import {OpTitleService} from 'core-components/html/op-title.service';
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {
IWorkPackageEditingServiceToken
} from "../wp-edit-form/work-package-editing.service.interface";
import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface";
import {CurrentUserService} from "core-app/components/user/current-user.service";
@ -57,7 +52,6 @@ export class WorkPackageCreateController implements OnInit, OnDestroy {
public newWorkPackage:WorkPackageResource;
public parentWorkPackage:WorkPackageResource;
public changeset:WorkPackageChangeset;
protected wpEditing:WorkPackageEditingService = this.injector.get<WorkPackageEditingService>(IWorkPackageEditingServiceToken);
public stateParams = this.$transition.params('to');
public text = {
@ -81,16 +75,14 @@ export class WorkPackageCreateController implements OnInit, OnDestroy {
}
public ngOnInit() {
this.newWorkPackageFromParams(this.stateParams)
this
.createdWorkPackage()
.then((changeset:WorkPackageChangeset) => {
this.changeset = changeset;
this.newWorkPackage = changeset.workPackage;
this.setTitle();
this.wpCacheService.updateWorkPackage(this.newWorkPackage);
this.wpEditing.updateValue('new', changeset);
if (this.stateParams['parent_id']) {
this.changeset.setValue(
'parent',
@ -137,31 +129,15 @@ export class WorkPackageCreateController implements OnInit, OnDestroy {
this.titleService.setFirstPart(this.I18n.t('js.work_packages.create.title'));
}
protected newWorkPackageFromParams(stateParams:any):Promise<WorkPackageChangeset> {
const type = parseInt(stateParams.type);
// If there is an open edit for this type, continue it
const changeset = this.wpEditing.state('new').value;
if (changeset !== undefined) {
const changeType = changeset.workPackage.type;
const hasChanges = !changeset.empty;
const typeEmpty = (!changeType && !type);
const typeMatches = (changeType && changeType.idFromLink === type.toString());
if (hasChanges && (typeEmpty || typeMatches)) {
return Promise.resolve(changeset);
}
}
return this.wpCreate.createNewTypedWorkPackage(stateParams.projectPath, type).then(changeset => {
const filter = new WorkPackageFilterValues(this.injector, changeset, this.wpTableFilters.current, ['type']);
return filter.applyDefaultsFromFilters().then(() => changeset);
});
}
public cancelAndBackToList() {
this.wpEditing.stopEditing(this.newWorkPackage.id);
this.wpCreate.cancelCreation();
this.$state.go('work-packages.list', this.$state.params);
}
protected createdWorkPackage() {
const type = this.stateParams.type ? parseInt(this.stateParams.type) : undefined;
const project = this.stateParams.projectPath;
return this.wpCreate.createOrContinueWorkPackage(project, type);
}
}

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {Injectable, Injector} from '@angular/core';
import {Injectable, Injector, Inject} from '@angular/core';
import {ApiWorkPackagesService} from '../api/api-work-packages/api-work-packages.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
@ -36,10 +36,15 @@ import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-r
import {HalResourceService} from 'core-app/modules/hal/services/hal-resource.service';
import {IWorkPackageCreateService} from "core-components/wp-new/wp-create.service.interface";
import {HookService} from 'core-app/modules/plugins/hook-service';
import {WorkPackageFilterValues} from "core-components/wp-edit-form/work-package-filter-values";
import {IWorkPackageEditingServiceToken} from "core-components/wp-edit-form/work-package-editing.service.interface";
import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service";
import {WorkPackageTableFiltersService} from "core-components/wp-fast-table/state/wp-table-filters.service";
import {TableState} from "core-components/wp-table/table-state/table-state";
@Injectable()
export class WorkPackageCreateService implements IWorkPackageCreateService {
protected form:Promise<HalResource>;
protected form:Promise<HalResource>|undefined;
// Allow callbacks to happen on newly created work packages
protected newWorkPackageCreatedSubject = new Subject<WorkPackageResource>();
@ -48,10 +53,13 @@ export class WorkPackageCreateService implements IWorkPackageCreateService {
protected hooks:HookService,
protected wpCacheService:WorkPackageCacheService,
protected halResourceService:HalResourceService,
@Inject(IWorkPackageEditingServiceToken) protected readonly wpEditing:WorkPackageEditingService,
protected readonly tableState:TableState,
protected apiWorkPackages:ApiWorkPackagesService) {
}
public newWorkPackageCreated(wp:WorkPackageResource) {
this.form = undefined;
this.newWorkPackageCreatedSubject.next(wp);
}
@ -114,4 +122,92 @@ export class WorkPackageCreateService implements IWorkPackageCreateService {
return this.form;
}
public cancelCreation() {
this.wpEditing.stopEditing('new');
this.wpCacheService.clearSome('new');
}
public changesetUpdates$() {
return this
.wpEditing
.state('new')
.values$();
}
public createOrContinueWorkPackage(projectIdentifier:string, type?:number) {
let changesetPromise = this.continueExistingEdit(type);
if (!changesetPromise) {
changesetPromise = this.createNewWithDefaults(projectIdentifier, type);
}
return changesetPromise.then((changeset) => {
this.wpEditing.updateValue('new', changeset);
this.wpCacheService.updateWorkPackage(changeset.workPackage);
return changeset;
});
}
protected continueExistingEdit(type?:number) {
const changeset = this.wpEditing.state('new').value;
if (changeset !== undefined) {
const changeType = changeset.workPackage.type;
const hasChanges = !changeset.empty;
const typeEmpty = !changeType && !type;
const typeMatches = type && changeType && changeType.idFromLink === type.toString();
if (hasChanges && (typeEmpty || typeMatches)) {
return Promise.resolve(changeset);
}
}
return null;
}
protected createNewWithDefaults(projectIdentifier:string, type?:number) {
let changesetPromise = null;
if (type) {
changesetPromise = this.createNewTypedWorkPackage(projectIdentifier, type);
} else {
changesetPromise = this.createNewWorkPackage(projectIdentifier);
}
return changesetPromise.then((changeset:WorkPackageChangeset) => {
if (!changeset) {
throw 'No new work package was created';
}
let except:string[] = [];
if (type) {
except = ['type'];
}
this.applyDefaults(changeset, changeset.workPackage, except);
return changeset;
});
}
/**
* Apply values to the work package from the current set of filters
*
* @param changeset
* @param wp
* @param except
*/
private applyDefaults(changeset:WorkPackageChangeset, wp:WorkPackageResource, except:string[]) {
// Not using WorkPackageTableFiltersService here as the embedded table does not load the form
// which will result in that service having empty current filters.
let query = this.tableState.query.value;
if (query) {
const filter = new WorkPackageFilterValues(this.injector, changeset, query.filters, except);
filter.applyDefaultsFromFilters();
}
}
}

@ -29,6 +29,8 @@ import {OpModalService} from 'core-components/op-modals/op-modal.service';
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {WorkPackageEmbeddedBaseComponent} from "core-components/wp-table/embedded/wp-embedded-base.component";
import {WorkPackageTableHighlightingService} from "core-components/wp-fast-table/state/wp-table-highlighting.service";
import {WorkPackageCreateService} from "core-components/wp-new/wp-create.service";
import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface";
@Component({
selector: 'wp-embedded-table',
@ -49,6 +51,7 @@ import {WorkPackageTableHighlightingService} from "core-components/wp-fast-table
WorkPackageTableAdditionalElementsService,
WorkPackageTableRefreshService,
WorkPackageTableHighlightingService,
{ provide: IWorkPackageCreateServiceToken, useClass: WorkPackageCreateService },
// Order is important here, to avoid this service
// getting global injections
WorkPackageStatesInitializationService,

@ -74,8 +74,8 @@ export class EditFieldComponent extends Field implements OnDestroy {
)
.subscribe((changeset) => {
if (!this.changeset.empty && this.changeset.wpForm) {
const fieldSchema = changeset.wpForm!.schema[this.name];
if (!this.changeset.empty && this.changeset.form) {
const fieldSchema = changeset.form!.schema[this.name];
if (!fieldSchema) {
return handler.deactivate(false);

@ -306,6 +306,7 @@ describe 'new work package', js: true do
end
it 'can create the work package, but not update it after saving' do
type_field.activate!
type_field.set_value type_bug.name
# wait after the type change
sleep(0.2)
@ -321,7 +322,7 @@ describe 'new work package', js: true do
end
end
context 'a anonymous user is prompted to login' do
context 'an anonymous user is prompted to login' do
let(:user) { FactoryBot.create(:anonymous) }
let(:wp_page) { ::Pages::Page.new }

@ -8,16 +8,16 @@ describe 'inline create work package', js: true do
let(:role) { FactoryBot.create :role, permissions: permissions }
let(:user) do
FactoryBot.create :user,
member_in_project: project,
member_through_role: role
member_in_project: project,
member_through_role: role
end
let(:status) { FactoryBot.create(:default_status) }
let(:workflow) do
FactoryBot.create :workflow,
type_id: type.id,
old_status: status,
new_status: FactoryBot.create(:status),
role: role
type_id: type.id,
old_status: status,
new_status: FactoryBot.create(:status),
role: role
end
let!(:project) { FactoryBot.create(:project, is_public: true, types: types) }
@ -88,6 +88,50 @@ describe 'inline create work package', js: true do
expect(page).to have_no_selector('.wp-inline-create--add-link')
end
end
context 'when having filtered by custom field and switching to that type' do
let(:cf_list) do
FactoryBot.create(:list_wp_custom_field, is_for_all: true, is_filter: true)
end
let(:types) { [type, cf_type] }
let(:type) { FactoryBot.create(:type_standard) }
let(:cf_type) { FactoryBot.create(:type, custom_fields: [cf_list]) }
let(:columns) { ::Components::WorkPackages::Columns.new }
it 'applies the filter value for the custom field' do
wp_table.visit!
wp_table.add_filter cf_list.name, 'is', cf_list.custom_options.second.name
columns.open_modal
columns.add(cf_list.name, save_changes: true)
wp_table.click_inline_create
callback.call
type_field = wp_table.edit_field(nil, :type)
type_field.activate!
type_field.set_value cf_type.name
wp_table.expect_notification(
type: :error,
message: 'Subject can\'t be blank.'
)
subject_field = wp_table.edit_field(nil, :subject)
subject_field.expect_active!
subject_field.set_value 'Some subject'
subject_field.save!
wp_table.expect_notification(
message: 'Successful creation. Click here to open this work package in fullscreen view.'
)
created_wp = WorkPackage.last
cf_field = wp_table.edit_field(created_wp, :"customField#{cf_list.id}")
cf_field.expect_text(cf_list.custom_options.second.name)
end
end
end
describe 'global create' do
@ -98,7 +142,7 @@ describe 'inline create work package', js: true do
end
it_behaves_like 'inline create work package' do
let(:callback) {
let(:callback) do
->() {
# Set project
project_field = wp_table.edit_field(nil, :project)
@ -112,7 +156,7 @@ describe 'inline create work package', js: true do
type_field.set_value type.name
}
}
end
end
end
@ -124,26 +168,26 @@ describe 'inline create work package', js: true do
end
it_behaves_like 'inline create work package' do
let(:callback) {
->() { }
}
let(:callback) do
->() {}
end
end
context 'user has permissions in other project' do
let(:permissions) { [:view_work_packages] }
let(:project2) { FactoryBot.create :project }
let(:role2) {
let(:role2) do
FactoryBot.create :role,
permissions: [:view_work_packages,
:add_work_packages]
}
let!(:membership) {
permissions: %i[view_work_packages
add_work_packages]
end
let!(:membership) do
FactoryBot.create :member,
user: user,
project: project2,
roles: [role2]
}
user: user,
project: project2,
roles: [role2]
end
it 'renders the work packages, but no create' do
wp_table.expect_work_package_listed(existing_wp)

@ -270,11 +270,6 @@ describe 'Switching types in work package table', js: true do
member_through_role: role
end
before do
workflow
login_as user
end
let(:custom_field) do
FactoryBot.create(
:list_wp_custom_field,
@ -309,6 +304,7 @@ describe 'Switching types in work package table', js: true do
end
before do
workflow
login_as(user)
visit new_project_work_packages_path(project.identifier, type: type.id)

@ -145,7 +145,6 @@ module Components
apply if save_changes
end
def apply
@opened = false

@ -153,7 +153,6 @@ module Components
expect(page).to have_no_selector '.wp-breadcrumb-parent', wait: 10
end
def remove_parent
# Open the parent edit
find('.wp-relation--parent-change').click

Loading…
Cancel
Save