Remove workPackageId number|string dualism

States always expect strings, so we should treat the ids as such.
pull/5117/head
Oliver Günther 8 years ago
parent 3662b9681e
commit 8a5dbbb043
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 4
      frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts
  2. 2
      frontend/app/components/api/api-work-packages/api-work-packages.service.ts
  3. 11
      frontend/app/components/routing/wp-list/wp-list.controller.ts
  4. 2
      frontend/app/components/states.service.ts
  5. 8
      frontend/app/components/work-packages/work-package-cache.service.test.ts
  6. 4
      frontend/app/components/work-packages/work-package-cache.service.ts
  7. 4
      frontend/app/components/work-packages/wp-watcher-button/wp-watcher-button.directive.ts
  8. 2
      frontend/app/components/wp-display/field-types/wp-display-id-field.module.ts
  9. 2
      frontend/app/components/wp-edit-form/work-package-edit-form.ts
  10. 2
      frontend/app/components/wp-fast-table/builders/details-link-builder.ts
  11. 16
      frontend/app/components/wp-fast-table/builders/row-builder.ts
  12. 8
      frontend/app/components/wp-fast-table/builders/ui-state-link-builder.ts
  13. 6
      frontend/app/components/wp-fast-table/handlers/cell/edit-cell-handler.ts
  14. 21
      frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts
  15. 98
      frontend/app/components/wp-fast-table/wp-fast-table.ts
  16. 5
      frontend/app/components/wp-fast-table/wp-table.interfaces.ts
  17. 2
      frontend/app/components/wp-panels/activity-panel/activity-panel.directive.ts
  18. 6
      frontend/app/components/wp-panels/watchers-panel/watchers-panel.controller.ts
  19. 2
      frontend/app/components/wp-relations/wp-relations-create/wp-relations-create.directive.ts
  20. 2
      frontend/app/components/wp-relations/wp-relations-hierarchy-row/wp-relations-hierarchy-row.directive.ts
  21. 4
      frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
  22. 8
      frontend/app/components/wp-relations/wp-relations.directive.ts
  23. 2
      lib/redmine/wiki_formatting/textile/helper.rb
  24. 2
      spec/services/authorization/query_transformation_spec.rb

@ -116,7 +116,7 @@ export class WorkPackageResource extends HalResource {
public $embedded: WorkPackageResourceEmbedded;
public $links: WorkPackageResourceLinks;
public id: number|string;
public id: string;
public schema: SchemaResource;
public $pristine: { [attribute: string]: any } = {};
public parentId: number;
@ -133,7 +133,7 @@ export class WorkPackageResource extends HalResource {
private form;
public get isNew(): boolean {
return isNaN(Number(this.id));
return this.id === 'new';
}
public get isMilestone(): boolean {

@ -64,7 +64,7 @@ export class ApiWorkPackagesService {
* @param force Bypass any cached value?
* @returns {IPromise<any>|IPromise<WorkPackageResource>} A promise for the WorkPackage.
*/
public loadWorkPackageById(id:number, force = false) {
public loadWorkPackageById(id:string, force = false) {
const url = this.v3Path.wp({wp: id});
return this.halRequest.get(url, null, {

@ -247,17 +247,6 @@ function WorkPackagesListController($scope,
$rootScope.$on('workPackagesRefreshInBackground', function () {
wpListService.fromQueryInstance($scope.query, $scope.projectIdentifier)
.then(function (json:api.ex.WorkPackagesMeta) {
var rowLookup = _.indexBy($scope.rows, (row:any) => row.object.id);
// Merge based on id and lockVersion
angular.forEach(json.work_packages, (fresh, i) => {
var staleRow = rowLookup[fresh.id];
if (staleRow && staleRow.object.lockVersion === fresh.lockVersion) {
json.work_packages[i] = staleRow.object;
}
});
$scope.$broadcast('openproject.workPackages.updateResults');
$scope.$evalAsync(_ => setupWorkPackagesTable(json));
});

@ -31,7 +31,7 @@ export class States {
};
// Current focused work package (e.g, row preselected for details button)
focusedWorkPackage = new State<number>();
focusedWorkPackage = new State<string>();
// Open editing forms
editing = new MultiState<WorkPackageEditForm>();

@ -64,9 +64,9 @@ describe('WorkPackageCacheService', () => {
wpCacheService.updateWorkPackageList(dummyWorkPackages);
let workPackage: WorkPackageResource;
wpCacheService.loadWorkPackage(1).observeOnScope(null).subscribe(wp => {
wpCacheService.loadWorkPackage('1').observeOnScope(null).subscribe(wp => {
workPackage = wp;
expect(workPackage.id).to.eq(1);
expect(workPackage.id).to.eq('1');
done();
});
@ -100,8 +100,8 @@ describe('WorkPackageCacheService', () => {
wpCacheService.updateWorkPackageList([workPackage]);
$rootScope.$apply();
wpCacheService.loadWorkPackage(1).observeOnScope(null).subscribe((wp: any) => {
expect(wp.id).to.eq(1);
wpCacheService.loadWorkPackage('1').observeOnScope(null).subscribe((wp: any) => {
expect(wp.id).to.eq('1');
expect(wp.dummy).to.eq(expected);
expected += 1;

@ -80,7 +80,7 @@ export class WorkPackageCacheService {
}
}
loadWorkPackage(workPackageId: number, forceUpdate = false): State<WorkPackageResource> {
loadWorkPackage(workPackageId: string, forceUpdate = false): State<WorkPackageResource> {
const state = this.states.workPackages.get(getWorkPackageId(workPackageId));
if (forceUpdate) {
state.clear();
@ -89,7 +89,7 @@ export class WorkPackageCacheService {
// Several services involved in the creation of work packages
// use this method to resolve the latest created work package,
// so let them just subscribe.
if (workPackageId.toString() === 'new') {
if (workPackageId === 'new') {
return state;
}

@ -45,7 +45,7 @@ export class WorkPackageWatcherButtonController {
public I18n,
public wpCacheService:WorkPackageCacheService) {
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
wpCacheService.loadWorkPackage(this.workPackage.id).observeOnScope($scope)
.subscribe((wp: WorkPackageResourceInterface) => {
this.workPackage = wp;
this.setWatchStatus();
@ -64,7 +64,7 @@ export class WorkPackageWatcherButtonController {
const toggleLink = this.nextStateLink();
toggleLink(toggleLink.$link.payload).then(() => {
this.wpCacheService.loadWorkPackage(<number> this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
});
};

@ -59,7 +59,7 @@ export class IdDisplayField extends DisplayField {
}
let link = UiStateLinkBuilder.linkToShow(
<number> this.value,
this.value,
displayText,
this.value
);

@ -63,7 +63,7 @@ export class WorkPackageEditForm {
// The last field that got activated
public lastActiveField:string;
constructor(public workPackageId:number,
constructor(public workPackageId:string,
public editMode = false) {
injectorBridge(this);

@ -22,7 +22,7 @@ export class DetailsLinkBuilder {
td.classList.add('wp-table--details-column', 'hide-when-print', '-short');
let detailsLink = UiStateLinkBuilder.linkToDetails(
<number> workPackage.id,
workPackage.id,
this.text.button,
''
);

@ -3,11 +3,13 @@ import {CellBuilder} from './cell-builder';
import {States} from '../../states.service';
import {injectorBridge} from '../../angular/angular-injector-bridge.functions';
import {DetailsLinkBuilder} from './details-link-builder';
import {WorkPackageTableSelection} from '../state/wp-table-selection.service';
export const rowClassName = 'wp-table--row';
export class RowBuilder {
// Injections
public states:States;
public wpTableSelection:WorkPackageTableSelection;
public I18n:op.I18n;
// Cell builder instance
@ -36,13 +38,6 @@ export class RowBuilder {
}
public build(workPackage:WorkPackageResource, row:HTMLElement) {
// Temporary check whether schema is available
// This shouldn't be necessary with the queries refactor
if (!workPackage.schema.$loaded) {
workPackage.schema.$load();
return;
}
row.id = `wp-row-${workPackage.id}`;
row.classList.add('wp-table--row', 'wp--row', 'issue');
@ -53,8 +48,13 @@ export class RowBuilder {
// Last column: details link
this.detailsLinkBuilder.build(workPackage, row);
// Set the row selection state
if (this.wpTableSelection.isSelected(<string>workPackage.id)) {
row.classList.add('-checked');
}
}
}
RowBuilder.$inject = ['states', 'I18n'];
RowBuilder.$inject = ['states', 'wpTableSelection', 'I18n'];

@ -2,19 +2,19 @@ export const uiStateLinkClass = '__ui-state-link';
export class UiStateLinkBuilder {
public static linkToDetails(workPackageId:number, title:string, content:string) {
public static linkToDetails(workPackageId:string, title:string, content:string) {
return this.build(workPackageId, 'currentDetailsState', title, content);
}
public static linkToShow(workPackageId:number, title:string, content:string) {
public static linkToShow(workPackageId:string, title:string, content:string) {
return this.build(workPackageId, 'currentShowState', title, content);
}
private static build(workPackageId:number, state:string, title:string, content:string) {
private static build(workPackageId:string, state:string, title:string, content:string) {
let a = document.createElement('a');
a.classList.add(uiStateLinkClass);
a.dataset['workPackageId'] = workPackageId.toString();
a.dataset['workPackageId'] = workPackageId;
a.dataset['wpState'] = state;
a.setAttribute('title', title);

@ -57,7 +57,7 @@ export class EditCellHandler extends ClickOrEnterHandler implements TableEventHa
return false;
}
private startEditing(state, workPackageId:number):WorkPackageEditForm {
private startEditing(state, workPackageId:string):WorkPackageEditForm {
let form = new WorkPackageEditForm(workPackageId);
state.put(form);
return form;
@ -66,8 +66,8 @@ export class EditCellHandler extends ClickOrEnterHandler implements TableEventHa
/**
* Retrieve the edit state for this work package
*/
private editState(workPackageId:number):State<WorkPackageEditForm> {
return this.states.editing.get(workPackageId.toString());
private editState(workPackageId:string):State<WorkPackageEditForm> {
return this.states.editing.get(workPackageId);
}
}

@ -2,6 +2,7 @@ import {States} from '../../states.service';
import {opServicesModule} from '../../../angular-modules';
import {State} from '../../../helpers/reactive-fassade';
import {WPTableRowSelectionState, WorkPackageTableRow} from '../wp-table.interfaces';
import {WorkPackageResource} from '../../api/api-v3/hal-resources/work-package-resource.service';
export class WorkPackageTableSelection {
@ -15,14 +16,18 @@ export class WorkPackageTableSelection {
}
}
public isSelected(workPackageId:string) {
return this.currentState.selected[workPackageId];
}
/**
* Select all work packages
*/
public selectAll(rows: WorkPackageTableRow[]) {
public selectAll(rows: string[]) {
const state = this._emptyState;
rows.forEach((row) => {
state.selected[row.workPackageId] = true;
rows.forEach((workPackageId) => {
state.selected[workPackageId] = true;
});
this.selectionState.put(state);
@ -55,7 +60,7 @@ export class WorkPackageTableSelection {
* Toggle a single row selection state and update the state.
* @param workPackageId
*/
public toggleRow(workPackageId:number) {
public toggleRow(workPackageId:string) {
let isSelected = this.currentState.selected[workPackageId];
this.setRowState(workPackageId, !isSelected);
}
@ -65,7 +70,7 @@ export class WorkPackageTableSelection {
* @param workPackageId
* @param newState
*/
public setRowState(workPackageId:number, newState:boolean) {
public setRowState(workPackageId:string, newState:boolean) {
let state = this.currentState;
state.selected[workPackageId] = newState;
this.selectionState.put(state);
@ -91,7 +96,7 @@ export class WorkPackageTableSelection {
* @param rows Current visible rows
* @param selected Selection target
*/
public setMultiSelectionFrom(rows:WorkPackageTableRow[], selected:WorkPackageTableRow) {
public setMultiSelectionFrom(rows:string[], selected:WorkPackageTableRow) {
let state = this.currentState;
if (this.selectionCount === 0) {
@ -101,8 +106,8 @@ export class WorkPackageTableSelection {
let start = Math.min(selected.position, state.activeRowIndex);
let end = Math.max(selected.position, state.activeRowIndex);
rows.forEach((row, i) => {
state.selected[row.object.id] = i >= start && i <= end;
rows.forEach((workPackageId, i) => {
state.selected[workPackageId] = i >= start && i <= end;
});
}

@ -8,6 +8,7 @@ import {TableEventsRegistry} from './handlers/table-events-registry';
import {Observable} from 'rxjs';
import {WorkPackageTableRow, WPTableRowSelectionState} from './wp-table.interfaces';
import {WorkPackageTableSelection} from './state/wp-table-selection.service';
interface WorkPackageRow {
workPackage:WorkPackageResource;
@ -23,8 +24,8 @@ export class WorkPackageTable {
public states:States;
public I18n:op.I18n;
public rows: WorkPackageTableRow[];
public rowIndex:{[id: number]: WorkPackageTableRow};
public rows: string[];
public rowIndex:{[id: string]: WorkPackageTableRow};
// Row builder instance
private rowBuilder = new RowBuilder();
@ -39,77 +40,92 @@ export class WorkPackageTable {
return this.rowIndex[workPackageId];
}
public initialize(rows:WorkPackageResource[]) {
/**
* Build the row index and positions from the given set of ordered work packages.
* @param rows
*/
private buildIndex(rows) {
this.rowIndex = {};
this.rows = rows.map((wp:WorkPackageResource, i:number) => {
let row = <WorkPackageTableRow> { object: wp, workPackageId: wp.id, position: i };
this.rowIndex[wp.id] = row;
return row;
let id = wp.id;
this.rowIndex[id] = <WorkPackageTableRow> { object: wp, workPackageId: id, position: i };
return id;
});
}
// Draw work packages
this.refreshAllWorkPackages();
// Observe changes on the work packages multistate
this.states.workPackages.observe(null).subscribe((changedId:string) => {
/**
* Observe the WP multi state for _any_ change on the known work packages.
* If a visible row is affected, refresh it immediately.
*/
private observeRowChanges() {
this.states.workPackages.observe(null)
.subscribe(([changedId, wp]: [string, WorkPackageResource]) => {
let row = this.rowIndex[changedId];
if (row !== undefined) {
this.refreshWorkPackage(row.object);
row.object = wp;
this.refreshWorkPackage(row);
this.rowIndex[changedId] = row;
}
});
}
/**
*
* @param rows
*/
public initialSetup(rows:WorkPackageResource[]) {
// Build the row representation
this.buildIndex(rows);
// Draw work packages
this.refreshAllWorkPackages();
// Observe changes on the work packages multistate
this.observeRowChanges();
// Preselect first work package as focused
if (this.rows.length) {
this.states.focusedWorkPackage.put(this.rows[0].workPackageId);
this.states.focusedWorkPackage.put(this.rows[0]);
}
}
public refreshAllWorkPackages() {
let tbodyContent = document.createDocumentFragment();
let selection = this.states.table.selection.getCurrentValue();
let times = 0;
this.rows.forEach((row:WorkPackageTableRow) => {
this.rows.forEach((wpId:string) => {
let row = this.rowIndex[wpId];
var t0 = performance.now();
let tr = this.rowBuilder.createEmptyRow(row.object);
this.rowBuilder.build(row.object, tr);
var t1 = performance.now();
times += (t1-t0);
if (selection.selected[row.workPackageId]) {
tr.classList.add('-checked');
}
row.element = tr;
tbodyContent.appendChild(tr);
});
console.log("Rows took " + (times / this.rows.length) + " ms on average.");
console.log("Inner refresh " + times );
this.tbody.innerHTML = '';
this.tbody.appendChild(tbodyContent);
}
public refreshWorkPackage(workPackage) {
public refreshWorkPackage(row:WorkPackageTableRow) {
// If the work package is dirty, we're working on it
if (workPackage.dirty) {
console.log("Skipping row " + workPackage.id + " since its dirty");
if (row.object.dirty) {
console.log("Skipping row " + row.workPackageId + " since its dirty");
return;
}
// Get the row for the WP if refreshing existing
let oldRow = <HTMLElement> document.getElementById('wp-row-' + workPackage.id);
let oldRow = row.element || this.locateRow(row.workPackageId);
if (oldRow.dataset['lockVersion'] === workPackage.lockVersion.toString()) {
console.log("Skipping row " + workPackage.id + " since its fresh");
if (oldRow.dataset['lockVersion'] === row.object.lockVersion.toString()) {
console.log("Skipping row " + row.workPackageId + " since its fresh");
return;
}
let newRow = this.rowBuilder.createEmptyRow(workPackage);
this.rowBuilder.build(workPackage, newRow);
let newRow = this.rowBuilder.createEmptyRow(row.object);
this.rowBuilder.build(row.object, newRow);
oldRow.parentNode.replaceChild(newRow, oldRow);
row.element = newRow;
}
private renderSelectionState(state:WPTableRowSelectionState) {
@ -120,15 +136,17 @@ export class WorkPackageTable {
});
}
private locateRow(id):HTMLElement {
return document.getElementById('wp-row-' + id);
}
private initializeStates() {
// Redraw table if rows changed
this.states.table.rows.observe(null).subscribe((rows:WorkPackageResource[]) => {
this.states.table.columns.get().then(() => {
var t0 = performance.now();
this.initialize(rows);
var t1 = performance.now();
console.log("Initialize took " + (t1 - t0) + " milliseconds.");
});
var t0 = performance.now();
this.initialSetup(rows);
var t1 = performance.now();
console.log("Initialize took " + (t1 - t0) + " milliseconds.");
});
this.states.table.columns.observe(null).subscribe(() => {

@ -1,12 +1,13 @@
import {WorkPackageResource} from '../api/api-v3/hal-resources/work-package-resource.service';
export interface WorkPackageTableRow {
object:WorkPackageResource;
workPackageId:number;
workPackageId:string;
position:number;
element?:HTMLElement;
}
export interface WorkPackageTableRowsState {
[workPackageId:number]:WorkPackageTableRow;
[workPackageId:string]:WorkPackageTableRow;
}
export interface WorkPackageTableColumns {

@ -42,7 +42,7 @@ export class ActivityPanelController {
this.reverse = wpActivity.order === 'asc';
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
wpCacheService.loadWorkPackage(this.workPackage.id).observeOnScope($scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.wpActivity.aggregateActivities(this.workPackage).then(activities => {

@ -65,7 +65,7 @@ export class WatchersPanelController {
return;
}
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
wpCacheService.loadWorkPackage(this.workPackage.id).observeOnScope($scope)
.subscribe((wp: WorkPackageResourceInterface) => {
this.workPackage = wp;
this.loadCurrentWatchers();
@ -157,7 +157,7 @@ export class WatchersPanelController {
.then(() => {
// Forcefully reload the resource to update the watch/unwatch links
// should the current user have been added
this.wpCacheService.loadWorkPackage(<number> this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.autocompleteInput = '';
})
.catch((error) => this.wpNotificationsService.showError(error, this.workPackage));
@ -170,7 +170,7 @@ export class WatchersPanelController {
// Forcefully reload the resource to update the watch/unwatch links
// should the current user have been removed
this.wpCacheService.loadWorkPackage(<number> this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
})
.catch((error) => this.wpNotificationsService.showError(error, this.workPackage));
};

@ -76,7 +76,7 @@ export class WorkPackageRelationsCreateController {
protected addExistingChildRelation() {
return this.wpRelationsHierarchyService.addExistingChildWp(this.workPackage, this.selectedWpId)
.then(() => this.wpCacheService.loadWorkPackage(<number> this.workPackage.id, true))
.then(() => this.wpCacheService.loadWorkPackage(this.workPackage.id, true))
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.workPackage))
.finally(() => this.toggleRelationsCreateForm());
}

@ -70,7 +70,7 @@ class WpRelationsHierarchyRowDirectiveController {
this.wpRelationsHierarchyService
.removeChild(this.relatedWorkPackage)
.then(() => {
this.wpCacheService.loadWorkPackage(<number> this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.wpNotificationsService.showSave(this.workPackage);
this.$timeout(() => {
angular.element('#hierarchy--add-exisiting-child').focus();

@ -46,7 +46,7 @@ export class WorkPackageRelationsHierarchyController {
protected I18n:op.I18n) {
this.wpCacheService
.loadWorkPackage(<number> this.workPackage.id)
.loadWorkPackage(this.workPackage.id)
.observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
@ -71,7 +71,7 @@ export class WorkPackageRelationsHierarchyController {
}
this.wpCacheService
.loadWorkPackage(this.workPackage.parentId)
.loadWorkPackage(this.workPackage.parentId.toString())
.observeOnScope(this.$scope)
.take(1)
.subscribe((parent:WorkPackageResourceInterface) => {

@ -58,7 +58,7 @@ export class WorkPackageRelationsController {
// Listen for changes to this WP.
this.wpCacheService
.loadWorkPackage(<number> this.workPackage.id)
.loadWorkPackage(this.workPackage.id)
.observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
@ -66,8 +66,10 @@ export class WorkPackageRelationsController {
});
}
protected getRelatedWorkPackages(workPackageIds:number[]) {
let observablesToGetZipped = workPackageIds.map(wpId => this.wpCacheService.loadWorkPackage(wpId).observeOnScope(this.$scope));
protected getRelatedWorkPackages(workPackageIds:string[]) {
let observablesToGetZipped = workPackageIds.map(wpId => {
return this.wpCacheService.loadWorkPackage(wpId).observeOnScope(this.$scope);
});
if (observablesToGetZipped.length > 1) {
return Observable

@ -45,7 +45,7 @@ module Redmine
title: ::I18n.t('js.inplace.link_formatting_help')
javascript_tag(<<-EOF)
// initialize the toolbar later, so that i18n-js has a chance to set the translations
// initialSetup the toolbar later, so that i18n-js has a chance to set the translations
// for the wiki-buttons first.
jQuery(document).ready(function(){
var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}'));

@ -43,7 +43,7 @@ describe Authorization::QueryTransformation do
block
end
context 'initialize' do
context 'initialSetup' do
it 'sets on' do
expect(instance.on).to eql on
end

Loading…
Cancel
Save