pull/6263/head
Oliver Günther 7 years ago
parent 30e893bf6c
commit 25ecdfb0b2
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 34
      frontend/app/angular4-modules.ts
  2. 2
      frontend/app/components/angular/simple-template-renderer.ts
  3. 291
      frontend/app/components/api/api-v3/hal-link/hal-link.service.test.ts
  4. 241
      frontend/app/components/api/api-v3/hal-request/hal-request.service.test.ts
  5. 114
      frontend/app/components/api/api-v3/hal-resource-dms/relations-dm.service.test.ts
  6. 23
      frontend/app/components/api/api-v3/hal-resource-factory/hal-changeset-proxy.ts
  7. 109
      frontend/app/components/api/api-v3/hal-resource-factory/hal-resource-factory.service.test.ts
  8. 92
      frontend/app/components/api/api-v3/hal-resource-types/hal-resource-types.config.ts
  9. 133
      frontend/app/components/api/api-v3/hal-resource-types/hal-resource-types.service.test.ts
  10. 85
      frontend/app/components/api/api-v3/hal-resource-types/hal-resource-types.service.ts
  11. 74
      frontend/app/components/api/api-v3/hal-resources/attachment-collection-resource.service.test.ts
  12. 60
      frontend/app/components/api/api-v3/hal-resources/attachment-collection-resource.service.ts
  13. 76
      frontend/app/components/api/api-v3/hal-resources/collection-resource.service.test.ts
  14. 55
      frontend/app/components/api/api-v3/hal-resources/custom-action-resource.service.ts
  15. 726
      frontend/app/components/api/api-v3/hal-resources/hal-resource.service.test.ts
  16. 394
      frontend/app/components/api/api-v3/hal-resources/hal-resource.service.ts
  17. 41
      frontend/app/components/api/api-v3/hal-resources/query-schema-resource.service.ts
  18. 455
      frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.test.ts
  19. 60
      frontend/app/components/api/api-work-packages/api-work-packages.service.ts
  20. 141
      frontend/app/components/caching/cache-service.service.ts
  21. 2
      frontend/app/components/common/authoring/authoring.component.ts
  22. 2
      frontend/app/components/common/date/op-date-time.upgraded.directive.ts
  23. 6
      frontend/app/components/common/help-texts/attribute-help-text.directive.ts
  24. 21
      frontend/app/components/common/help-texts/attribute-help-text.service.ts
  25. 6
      frontend/app/components/common/textile/textile-service.ts
  26. 2
      frontend/app/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts
  27. 2
      frontend/app/components/filters/filter-boolean-value/filter-boolean-value.directive.ts
  28. 2
      frontend/app/components/filters/filter-date-time-value/filter-date-time-value.directive.ts
  29. 2
      frontend/app/components/filters/filter-date-times-value/filter-date-times-value.directive.ts
  30. 2
      frontend/app/components/filters/filter-date-value/filter-date-value.directive.ts
  31. 2
      frontend/app/components/filters/filter-dates-value/filter-dates-value.directive.ts
  32. 6
      frontend/app/components/filters/filter-integer-value/filter-integer-value.directive.ts
  33. 2
      frontend/app/components/filters/filter-string-value/filter-string-value.directive.ts
  34. 2
      frontend/app/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.directive.test.ts
  35. 19
      frontend/app/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.directive.ts
  36. 8
      frontend/app/components/filters/query-filter/query-filter.directive.ts
  37. 10
      frontend/app/components/filters/query-filters/query-filters.directive.ts
  38. 2
      frontend/app/components/modals/confirm-dialog/confirm-dialog.service.ts
  39. 16
      frontend/app/components/modals/export-modal/export-modal.controller.ts
  40. 5
      frontend/app/components/modals/save-modal/save-modal.controller.ts
  41. 2
      frontend/app/components/modals/share-modal/share-modal.controller.ts
  42. 2
      frontend/app/components/modals/sorting-modal/sorting-modal.controller.ts
  43. 12
      frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts
  44. 4
      frontend/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts
  45. 4
      frontend/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts
  46. 6
      frontend/app/components/op-context-menu/handlers/wp-create-settings-menu.directive.ts
  47. 8
      frontend/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts
  48. 4
      frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts
  49. 8
      frontend/app/components/routing/wp-full-view/wp-full-view.component.ts
  50. 2
      frontend/app/components/routing/wp-list/wp-list.component.ts
  51. 6
      frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts
  52. 17
      frontend/app/components/schemas/schema-cache.service.ts
  53. 16
      frontend/app/components/states.service.ts
  54. 6
      frontend/app/components/table-pagination/pagination-service.ts
  55. 2
      frontend/app/components/user/user-link/user-link.directive.ts
  56. 2
      frontend/app/components/user/user-link/user-link.upgraded.component.ts
  57. 4
      frontend/app/components/work-packages/work-package-authorization.service.ts
  58. 35
      frontend/app/components/work-packages/work-package-cache.service.test.ts
  59. 28
      frontend/app/components/work-packages/work-package-cache.service.ts
  60. 6
      frontend/app/components/work-packages/work-package-comment/work-package-comment.directive.ts
  61. 6
      frontend/app/components/work-packages/work-package-comment/work-package-comment.directive.upgraded.ts
  62. 4
      frontend/app/components/work-packages/work-package-comment/wp-comment-field.module.ts
  63. 4
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/drop-model.ts
  64. 7
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/work-package-field-model.ts
  65. 8
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts
  66. 4
      frontend/app/components/work-packages/wp-breadcrumb/wp-breadcrumb.component.ts
  67. 4
      frontend/app/components/work-packages/wp-editor-field/wp-editor-field.component.ts
  68. 12
      frontend/app/components/work-packages/wp-single-view/wp-single-view.component.ts
  69. 14
      frontend/app/components/work-packages/wp-subject/wp-subject.component.ts
  70. 4
      frontend/app/components/work-packages/wp-type-status/wp-type-status.component.ts
  71. 16
      frontend/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts
  72. 8
      frontend/app/components/wp-activity/activity-entry.directive.upgraded.ts
  73. 8
      frontend/app/components/wp-activity/activity-service.ts
  74. 131
      frontend/app/components/wp-activity/revision/revision-activity-directive.test.ts
  75. 9
      frontend/app/components/wp-activity/user/user-activity-directive.ts
  76. 6
      frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list-item.component.ts
  77. 6
      frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.component.ts
  78. 4
      frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload-ng1-wrapper.ts
  79. 4
      frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts
  80. 4
      frontend/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts
  81. 2
      frontend/app/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts
  82. 10
      frontend/app/components/wp-copy/wp-copy.controller.ts
  83. 2
      frontend/app/components/wp-custom-actions/wp-custom-actions.component.ts
  84. 20
      frontend/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts
  85. 4
      frontend/app/components/wp-details/wp-details-toolbar.component.ts
  86. 3
      frontend/app/components/wp-display/field-types/wp-display-duration-field.module.ts
  87. 2
      frontend/app/components/wp-display/field-types/wp-display-float-field.module.ts
  88. 2
      frontend/app/components/wp-display/field-types/wp-display-formattable-field.module.ts
  89. 4
      frontend/app/components/wp-display/field-types/wp-display-id-field.module.ts
  90. 4
      frontend/app/components/wp-display/field-types/wp-display-resources-field.module.ts
  91. 3
      frontend/app/components/wp-display/field-types/wp-display-spent-time-field.module.ts
  92. 3
      frontend/app/components/wp-display/field-types/wp-display-work-package-field.module.ts
  93. 6
      frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts
  94. 14
      frontend/app/components/wp-edit-form/display-field-renderer.ts
  95. 6
      frontend/app/components/wp-edit-form/single-view-edit-context.ts
  96. 14
      frontend/app/components/wp-edit-form/table-row-edit-context.ts
  97. 50
      frontend/app/components/wp-edit-form/work-package-changeset.ts
  98. 9
      frontend/app/components/wp-edit-form/work-package-edit-context.ts
  99. 4
      frontend/app/components/wp-edit-form/work-package-edit-field-handler.ts
  100. 20
      frontend/app/components/wp-edit-form/work-package-edit-form.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {InjectionToken, NgModule} from '@angular/core';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {UpgradeModule} from '@angular/upgrade/static';
import {FormsModule} from '@angular/forms';
@ -49,9 +49,7 @@ import {WorkPackageTableRelationColumnsService} from 'core-components/wp-fast-ta
import {WorkPackageTableSelection} from 'core-components/wp-fast-table/state/wp-table-selection.service';
import {WorkPackageTableSortByService} from 'core-components/wp-fast-table/state/wp-table-sort-by.service';
import {WorkPackageTableTimelineService} from 'core-components/wp-fast-table/state/wp-table-timeline.service';
import {
WorkPackageInlineCreateComponent,
} from 'core-components/wp-inline-create/wp-inline-create.component';
import {WorkPackageInlineCreateComponent,} from 'core-components/wp-inline-create/wp-inline-create.component';
import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service';
import {WorkPackageRelationsService} from 'core-components/wp-relations/wp-relations.service';
import {WpResizerDirectiveUpgraded} from 'core-components/wp-resizer/wp-resizer.directive';
@ -71,14 +69,21 @@ import {
$rootScopeToken,
$stateToken,
$timeoutToken,
columnsModalToken, exportModalToken,
FocusHelperToken, groupingModalToken,
halRequestToken, HalResourceToken,
columnsModalToken,
exportModalToken,
FocusHelperToken,
groupingModalToken,
halRequestToken,
HalResourceToken,
HookServiceToken,
I18nToken,
NotificationsServiceToken,
PathHelperToken, QueryFilterInstanceResourceToken, QueryResourceToken, saveModalToken,
settingsModalToken, shareModalToken,
PathHelperToken,
QueryFilterInstanceResourceToken,
QueryResourceToken,
saveModalToken,
settingsModalToken,
shareModalToken,
sortingModalToken,
timelinesModalToken,
TimezoneServiceToken,
@ -149,7 +154,6 @@ import {WorkPackageWatcherEntryComponent} from 'core-components/wp-single-view-t
import {WorkPackageNewFullViewComponent} from 'core-components/wp-new/wp-new-full-view.component';
import {WorkPackageTypeStatusComponent} from 'core-components/work-packages/wp-type-status/wp-type-status.component';
import {WorkPackageEditActionsBarComponent} from 'core-components/common/edit-actions-bar/wp-edit-actions-bar.component';
import {RootDmService} from 'core-components/api/api-v3/hal-resource-dms/root-dm.service';
import {WorkPackageCopyFullViewComponent} from 'core-components/wp-copy/wp-copy-full-view.component';
import {WorkPackageNewSplitViewComponent} from 'core-components/wp-new/wp-new-split-view.component';
import {WorkPackageCopySplitViewComponent} from 'core-components/wp-copy/wp-copy-split-view.component';
@ -169,10 +173,8 @@ import {WorkPackageCreateSettingsMenuDirective} from 'core-components/op-context
import {WorkPackageSingleContextMenuDirective} from 'core-components/op-context-menu/wp-context-menu/wp-single-context-menu';
import {WorkPackageQuerySelectableTitleComponent} from 'core-components/wp-query-select/wp-query-selectable-title.component';
import {WorkPackageQuerySelectDropdownComponent} from 'core-components/wp-query-select/wp-query-select-dropdown.component';
import {QueryDmService} from 'core-components/api/api-v3/hal-resource-dms/query-dm.service';
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {QueryMenuService} from 'core-components/wp-query-menu/wp-query-menu.service';
import {QueryFormDmService} from 'core-components/api/api-v3/hal-resource-dms/query-form-dm.service';
import {WorkPackageStatesInitializationService} from 'core-components/wp-list/wp-states-initialization.service';
import {WorkPackageTableAdditionalElementsService} from 'core-components/wp-fast-table/state/wp-table-additional-elements.service';
import {WorkPackagesListInvalidQueryService} from 'core-components/wp-list/wp-list-invalid-query.service';
@ -188,6 +190,8 @@ import {Ng1RelationsCreateWrapper} from 'core-components/wp-relations/wp-relatio
import {WpRelationsAutocompleteComponent} from 'core-components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component';
import {WpRelationAddChildComponent} from 'core-components/wp-relations/wp-relation-add-child/wp-relation-add-child';
import {WpRelationParentComponent} from 'core-components/wp-relations/wp-relations-parent/wp-relations-parent.component';
import {OpenprojectHalModule} from 'core-app/modules/hal/openproject-hal.module';
import {QueryFormDmService} from 'core-app/modules/dm-services/query-form-dm.service';
@NgModule({
imports: [
@ -196,7 +200,9 @@ import {WpRelationParentComponent} from 'core-components/wp-relations/wp-relatio
FormsModule,
UIRouterUpgradeModule,
// Angular CDK
PortalModule
PortalModule,
// Hal Module
OpenprojectHalModule
],
providers: [
GonRef,
@ -267,8 +273,6 @@ import {WpRelationParentComponent} from 'core-components/wp-relations/wp-relatio
upgradeService('authorisationService', AuthorisationService),
upgradeService('ConfigurationService', ConfigurationService),
upgradeService('currentProject', CurrentProjectService),
upgradeService('RootDm', RootDmService),
upgradeService('QueryDm', QueryDmService),
upgradeService('queryMenu', QueryMenuService),
// Split view
upgradeService('firstRoute', FirstRouteService),

@ -55,7 +55,7 @@ export class SimpleTemplateRenderer {
public renderIsolated(element:JQuery,
scope:ng.IScope,
template:string,
scopeValues:Object):ng.IPromise<ng.IAugmentedJQuery> {
scopeValues:Object):Promise<ng.IAugmentedJQuery> {
const deferred = this.$q.defer<ng.IAugmentedJQuery>();
_.assign(scope, scopeValues);

@ -1,291 +0,0 @@
//-- 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 {opApiModule, opServicesModule} from '../../../../angular-modules';
import {HalLink} from './hal-link.service';
import {HalRequestService} from '../hal-request/hal-request.service';
describe('HalLink service', () => {
var $httpBackend:ng.IHttpBackendService;
var $rootScope:any;
var HalLink:any;
var link:HalLink;
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name));
beforeEach(angular.mock.inject(function (_$httpBackend_:any,
_$rootScope_:any,
_HalLink_:any,
halRequest:HalRequestService) {
[$httpBackend, $rootScope, HalLink] = _.toArray(arguments);
halRequest.defaultHeaders.caching.enabled = false;
}));
it('should exist', () => {
expect(HalLink).to.exist;
});
describe('when creating a HalLink from an empty object', () => {
beforeEach(() => {
link = HalLink.fromObject({});
});
it('should have the "get" method as default', () => {
expect(link.method).to.eq('get');
});
it('should have a null href', () => {
expect(link.href).to.be.null;
});
it('should not be templated', () => {
expect(link.templated).to.be.false;
});
it('should have an empty string as title', () => {
expect(link.title).to.equal('');
});
});
describe('when fetching a link that has a null href', () => {
beforeEach(() => {
link = new HalLink();
link.href = null;
});
it('should return a promise that has null as its return value', () => {
expect(link.$fetch()).to.eventually.be.null;
$rootScope.$apply();
});
});
describe('when the method of the link is "delete"', () => {
beforeEach(() => {
link = HalLink.fromObject({
href: 'home',
method: 'delete'
});
});
it('should throw no error', () => {
expect(() => link.$fetch()).not.to.throw(Error);
});
});
describe('when passing headers to $fetch', () => {
beforeEach(() => {
link = HalLink.fromObject({href: 'foobar'});
link.$fetch({param: 'foo'}, {foo: 'bar'});
});
it('should send the headers', () => {
$httpBackend.expectGET('foobar?param=foo', (headers:any) => headers.foo === 'bar').respond(200, {});
$httpBackend.flush();
});
});
describe('when using the link', () => {
var response:any;
var result:any;
beforeEach(() => {
link = HalLink.fromObject({
href: '/api/link'
});
response = {
_links: {},
hello: 'world'
};
link.$fetch().then(val => result = val);
$httpBackend.expectGET('/api/link').respond(200, response);
$httpBackend.flush();
});
it('should return a promise that returns the given value', () => {
expect(result.hello).to.eq(response.hello);
});
it('should return a HalResource', () => {
expect(result.$isHal).to.be.true;
});
it('should perform a GET request by default', () => {
link.$fetch();
$httpBackend.expectGET('/api/link').respond(200, {});
$httpBackend.flush();
});
it('should send the provided data', () => {
const data = {hello: 'world'};
link.method = 'post';
link.$fetch(data);
$httpBackend.expect('POST', '/api/link', data).respond(200, {});
$httpBackend.flush();
});
it('should perform a POST request', () => {
link.method = 'post';
link.$fetch();
$httpBackend.expectPOST('/api/link').respond(200, {});
$httpBackend.flush();
});
it('should perform a PUT request', () => {
link.method = 'put';
link.$fetch();
$httpBackend.expectPUT('/api/link').respond(200, {});
$httpBackend.flush();
});
it('should perform a PATCH request', () => {
link.method = 'patch';
link.$fetch();
$httpBackend.expectPATCH('/api/link').respond(200, {});
$httpBackend.flush();
});
describe('when making the link callable', () => {
var func:any;
const runChecks = () => {
it('should return a function that fetches the data', () => {
func();
$httpBackend.expectPOST('foo').respond(200, {});
$httpBackend.flush();
});
it('should pass the params to $fetch', () => {
var $fetch = sinon.spy(link, '$fetch');
func('hello');
expect($fetch.calledWith('hello')).to.be.true;
});
it('should have the href property of the link', () => {
expect(func.href).to.equal(link.href);
});
it('should have the title property of the link', () => {
expect(func.title).to.equal(link.title);
});
it('should have the method property of the link', () => {
expect(func.method).to.equal(link.method);
});
it('should have the templated property of the link', () => {
expect(func.templated).to.equal(link.templated);
});
};
beforeEach(() => {
link.href = 'foo';
link.title = 'title';
link.method = 'post';
link.templated = true;
});
describe('when using the instance method', () => {
beforeEach(() => {
func = link.$callable();
});
runChecks();
});
describe('when using the static factory method', () => {
beforeEach(() => {
func = HalLink.callable(link);
link = func.$link;
});
runChecks();
});
});
describe('when $preparing the link', () => {
var func:any;
beforeEach(() => {
link.href = '/foo/bar/{user_id}';
link.title = 'title';
link.method = 'post';
link.templated = true;
});
describe('when the link is NOT templated', () => {
beforeEach(() => {
link.templated = false;
});
it('should raise an exception', () => {
expect(function() {
link.$prepare({});
}).to.throw;
});
});
describe('when the link is templated', () => {
beforeEach(() => {
func = link.$prepare({ user_id: '1234' });
});
it('should return a function that fetches the data', () => {
func();
$httpBackend.expectPOST('/foo/bar/1234').respond(200, {});
$httpBackend.flush();
});
it('should pass the params to $fetch', () => {
var $fetch = sinon.spy(func.$link, '$fetch');
func('hello');
expect($fetch.calledWith('hello')).to.be.true;
});
it('should have the untemplated href property', () => {
expect(func.href).to.equal('/foo/bar/1234');
});
it('should have the title property of the link', () => {
expect(func.title).to.equal(link.title);
});
it('should have the method property of the link', () => {
expect(func.method).to.equal(link.method);
});
it('should not be templated', () => {
expect(func.templated).to.equal(false);
});
});
});
});
});

@ -1,241 +0,0 @@
//-- 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 {opApiModule} from '../../../../angular-modules';
import {HalRequestService} from './hal-request.service';
import {HalResource} from '../hal-resources/hal-resource.service';
import IPromise = angular.IPromise;
import IRootScopeService = angular.IRootScopeService;
import IHttpBackendService = angular.IHttpBackendService;
describe('halRequest service', () => {
var $httpBackend:IHttpBackendService;
var $rootScope:IRootScopeService;
var halRequest:HalRequestService;
beforeEach(angular.mock.module(opApiModule.name));
beforeEach(angular.mock.inject(function (_$httpBackend_:any, _$rootScope_:any, _halRequest_:any) {
[$httpBackend, $rootScope, halRequest] = _.toArray(arguments);
}));
it('should exist', () => {
expect(halRequest).to.exist;
});
afterEach(() => {
$rootScope.$apply();
});
describe('when requesting a null href', () => {
var promise:any;
beforeEach(() => {
promise = halRequest.request('get', '');
});
it('should return a rejected promise', () => {
expect(promise).to.eventually.be.rejected;
});
});
describe('when requesting the same GET resource multiple times', () => {
var headers:any;
const testRequest = (prepare:any) => {
beforeEach(prepare);
it('should perform requests according to the cache options', () => {
$httpBackend.expectGET('something').respond(200, '', (headers:any) => {
return headers.caching.enabled === true;
});
if (headers.caching && headers.caching.enabled === false) {
$httpBackend.expectGET('something').respond(200, '');
}
});
};
const testMethods = () => {
describe('when using request()', () => {
testRequest(() => {
halRequest.request('get', 'something', {}, headers);
halRequest.request('get', 'something', {}, headers);
});
});
describe('when using get()', () => {
testRequest(() => {
halRequest.get('something', {}, headers);
halRequest.get('something', {}, headers);
});
});
};
beforeEach(() => {
halRequest.defaultHeaders.caching = {enabled: true};
headers = {};
});
testMethods();
describe('when sending a no cache header', () => {
beforeEach(() => {
headers = {caching: {enabled: false}};
});
testMethods();
});
});
describe('when requesting data', () => {
var promise:IPromise<HalResource>;
var method:string;
var data:any;
var expectedData:any;
var headers = {Accept: 'foo', caching: {enabled: false}};
const methods = ['get', 'put', 'post', 'patch', 'delete'];
const respond = (status:any, response:any) => {
$httpBackend
.expect(method.toUpperCase(), 'href', expectedData, (headers:any) => {
return headers.Accept === 'foo';
})
.respond(status, response);
$httpBackend.flush();
};
const runTests = () => {
describe('when no error occurs', () => {
beforeEach(() => respond(200, {}));
it('should return a HalResource', () => {
expect(promise).to.eventually.be.an.instanceOf(HalResource);
});
});
describe('when an error occurs', () => {
beforeEach(() => respond(400, {}));
it('should be rejected with an instance of HalResource', () => {
expect(promise).to.eventually.be.rejectedWith(HalResource);
});
});
describe('when the server does not respond with a result', () => {
beforeEach(() => respond(200, null));
it('should return nothing as well', () => {
expect(promise).to.eventually.be.null;
});
});
};
const runRequests = (cb:any) => {
const callback = () => {
if (method === 'get') {
data = null;
expectedData = null;
}
cb();
};
methods.forEach(requestMethod => {
describe(`when performing a ${requestMethod} request`, () => {
beforeEach(() => {
method = requestMethod;
});
describe('when sending no data with the request', () => {
beforeEach(() => {
data = void 0;
expectedData = void 0;
if (method === 'post') {
expectedData = {};
}
callback();
});
runTests();
});
describe('when sending data with the request', () => {
beforeEach(() => {
data = {foo: 'bar'};
expectedData = data;
callback();
});
runTests();
});
});
});
};
describe('when calling the http methods of the service', () => {
runRequests(() => {
promise = (halRequest as any)[method]('href', data, headers);
});
});
describe('when calling request()', () => {
runRequests(() => {
promise = halRequest.request(method, 'href', data, headers);
});
});
describe('when requesting a GET resource with parameters', () => {
const params = {foo: 'bar'};
beforeEach(() => {
promise = halRequest.get('href', params);
});
it('should append the parameters at the end of the requested url', () => {
$httpBackend.expectGET('href?foo=bar').respond(200, {});
$httpBackend.flush();
});
});
describe('#getAllPaginated', () => {
const params = {};
let promise:any;
beforeEach(() => {
promise = halRequest.getAllPaginated('href', 25, params);
$httpBackend.expectGET('href?offset=1').respond(200, { count: 12, total: 25 });
$httpBackend.expectGET('href?offset=2').respond(200, { count: 12, total: 25 });
$httpBackend.expectGET('href?offset=3').respond(200, { count: 1, total: 25 });
});
it('should resolve with three results', () => {
expect(promise).to.eventually.be.fulfilled.then(allResults => {
expect(allResults.length).to.eq(3);
});
});
});
});
});

@ -1,114 +0,0 @@
//-- 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 {opApiModule} from '../../../../angular-modules';
import IPromise = angular.IPromise;
import IRootScopeService = angular.IRootScopeService;
import IHttpBackendService = angular.IHttpBackendService;
import {RelationsDmService} from './relations-dm.service';
import {buildApiV3Filter} from '../api-v3-filter-builder';
describe('relationsDm service', () => {
var $httpBackend:IHttpBackendService;
var relationsDm:RelationsDmService;
var $rootScope:ng.IRootScopeService;
beforeEach(angular.mock.module(opApiModule.name));
beforeEach(angular.mock.inject(function (_relationsDm_:RelationsDmService, _$rootScope_:ng.IRootScopeService, _$httpBackend_:ng.IHttpBackendService) {
$httpBackend = _$httpBackend_;
relationsDm = _relationsDm_;
$rootScope = _$rootScope_;
}));
it('should exist', () => {
expect(relationsDm).to.exist;
});
afterEach(() => {
$rootScope.$apply();
});
function filterString(ids:string[]) {
let filterString = encodeURI(buildApiV3Filter('involved', '=', ids).toJson());
// Angular extends on encodeURI to unescape some values..
// https://github.com/angular/angular.js/blob/v1.5.x/src/Angular.js
filterString = filterString.replace(/=/gi, '%3D');
return '?filters=' + filterString;
}
describe('#loadInvolved', () => {
let promise:ng.IPromise<any>;
let ids:string[];
describe('when requesting some IDs', () => {
beforeEach(() => {
ids = ['1', '2', '3'];
promise = relationsDm.loadInvolved(ids);
});
it('should append the parameters at the end of the requested url', () => {
$httpBackend.expectGET('/api/v3/relations' + filterString(ids)).respond(200, { elements: ['foo'] });
$httpBackend.flush();
expect(promise).to.eventually.be.fulfilled.then((relations) => {
expect(relations).to.deep.equal(['foo']);
});
});
});
describe('when requesting with an invalid IDs', () => {
beforeEach(() => {
ids = ['1', 'foo'];
promise = relationsDm.loadInvolved(ids);
});
it('should append the parameters at the end of the requested url', () => {
$httpBackend.expectGET('/api/v3/relations' + filterString(['1'])).respond(200, { elements: ['foo'] });
$httpBackend.flush();
expect(promise).to.eventually.be.fulfilled.then((relations) => {
expect(relations).to.deep.equal(['foo']);
});
});
});
describe('when requesting with no valid IDs', () => {
beforeEach(() => {
ids = ['foo'];
promise = relationsDm.loadInvolved(ids);
});
it('should append the parameters at the end of the requested url', () => {
expect($httpBackend.flush.bind($httpBackend)).to.throw('No pending request to flush !');
expect(promise).to.eventually.be.fulfilled.then((relations) => {
expect(relations).to.be.empty;
});
});
});
});
});

@ -1,23 +0,0 @@
export interface ChangesetProxy {
changesetReset():void;
changesetPersist():void;
}
export function createChangeSetProxy<T>(target:T):T & ChangesetProxy {
const proxy = {} as any;
Object.setPrototypeOf(proxy, target);
proxy.changesetReset = () => {
_.forOwn(proxy, (value, key) => delete proxy[key]);
};
proxy.changesetPersist = () => {
_.forOwn(proxy, (value, key) => {
(target as any)[key] = value;
delete proxy[key];
});
};
return proxy;
}

@ -1,109 +0,0 @@
// -- 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 {opApiModule} from '../../../../angular-modules';
import {HalResourceFactoryService} from './hal-resource-factory.service';
import {HalResource} from '../hal-resources/hal-resource.service';
describe('halResourceFactory', () => {
var halResourceFactory:HalResourceFactoryService;
var resource:HalResource;
class OtherResource extends HalResource {
}
beforeEach(angular.mock.module(opApiModule.name));
beforeEach(angular.mock.module(opApiModule.name, ($provide:any) => {
$provide.value('OtherResource', OtherResource);
}));
beforeEach(angular.mock.inject(function (_halResourceFactory_:any) {
[halResourceFactory] = _.toArray(arguments);
}));
it('should exist', () => {
expect(halResourceFactory).to.exist;
});
it('should have HalResource as its default class', () => {
expect(halResourceFactory.defaultClass).to.equal(HalResource);
});
describe('when no resource type is configured', () => {
describe('when creating a resource', () => {
beforeEach(() => {
resource = halResourceFactory.createHalResource({});
});
it('should create an instance of the default type', () => {
expect(resource).to.be.an.instanceOf(halResourceFactory.defaultClass);
});
});
});
describe('when a resource type is configured', () => {
beforeEach(() => {
halResourceFactory.setResourceType('Other', OtherResource);
});
describe('when creating a resource of that type', () => {
beforeEach(() => {
resource = halResourceFactory.createHalResource({_type: 'Other'});
});
it('should be an instance of the configured type', () => {
expect(resource).to.be.an.instanceOf(OtherResource);
});
});
describe('when adding attribute configuration for that type', () => {
beforeEach(() => {
halResourceFactory.setResourceTypeAttributes('Other', {
attr: 'Other'
});
resource = halResourceFactory.createLinkedHalResource({}, 'Other', 'attr');
});
it('should be an instance of the configured attr type', () => {
expect(resource).to.be.an.instanceOf(OtherResource);
});
});
});
describe('when adding attr type configuration to for a non configured type', () => {
beforeEach(() => {
halResourceFactory.setResourceTypeAttributes('NonExistent', {
attr: 'NonExistent'
});
resource = halResourceFactory.createLinkedHalResource({}, 'NonExistent', 'attr');
});
it('should create a resource from the default tpye', () => {
expect(resource).to.be.an.instanceOf(halResourceFactory.defaultClass);
});
});
});

@ -1,92 +0,0 @@
// -- 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 {opApiModule} from '../../../../angular-modules';
import {HalResourceTypesService} from './hal-resource-types.service';
function halResourceTypesConfig(halResourceTypes:HalResourceTypesService) {
halResourceTypes.setResourceTypeConfig({
WorkPackage: {
className: 'WorkPackageResource',
attrTypes: {
parent: 'WorkPackage',
ancestors: 'WorkPackage',
children: 'WorkPackage',
relations: 'Relation',
schema: 'Schema',
type: 'Type'
}
},
Activity: {
user: 'User'
},
'Activity::Comment': {
user: 'User'
},
'Activity::Revision': {
user: 'User'
},
Relation: {
className: 'RelationResource',
attrTypes: {
from: 'WorkPackage',
to: 'WorkPackage'
}
},
Schema: 'SchemaResource',
Type: 'TypeResource',
SchemaDependency: 'SchemaDependencyResource',
Error: 'ErrorResource',
User: 'UserResource',
Collection: 'CollectionResource',
WorkPackageCollection: 'WorkPackageCollectionResource',
Query: {
className: 'QueryResource',
attrTypes: {
filters: 'QueryFilterInstance'
}
},
Form: 'FormResource',
QueryFilterInstance: {
className: 'QueryFilterInstanceResource' ,
attrTypes: {
schema: 'QueryFilterInstanceSchema',
filter: 'QueryFilter',
operator: 'QueryOperator'
}
},
QueryFilterInstanceSchema: 'QueryFilterInstanceSchemaResource',
QueryFilter: 'QueryFilterResource',
Root: 'RootResource',
QueryOperator: 'QueryOperatorResource',
HelpText: 'HelpTextResource',
CustomAction: 'CustomActionResource'
});
}
opApiModule.run(halResourceTypesConfig);

@ -1,133 +0,0 @@
// -- 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 {opApiModule} from '../../../../angular-modules';
import {HalResourceTypesService} from './hal-resource-types.service';
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service';
describe('halResourceTypes service', () => {
var halResourceTypes:HalResourceTypesService;
var halResourceFactory:HalResourceFactoryService;
var config:any;
var compareCls:typeof HalResource;
class HalResource {
}
class OtherResource {
}
class FooResource {
}
beforeEach(angular.mock.module(opApiModule.name, ($provide:any) => {
$provide.value('HalResource', HalResource);
$provide.value('OtherResource', OtherResource);
$provide.value('FooResource', FooResource);
}));
beforeEach(angular.mock.inject(function (_halResourceTypes_:any, _halResourceFactory_:any) {
[halResourceTypes, halResourceFactory] = _.toArray(arguments);
}));
const expectResourceClassAdded = () => {
it('should add the respective class object to the storage', () => {
const resource = halResourceFactory.createHalResource({_type: 'Other'});
expect(resource).to.be.an.instanceOf(compareCls);
});
};
const expectAttributeClassAdded = () => {
it('should add the attribute type config to the storage', () => {
const resource = halResourceFactory.createLinkedHalResource({}, 'Other', 'attr');
expect(resource).to.be.an.instanceOf(compareCls);
});
};
it('should exist', () => {
expect(halResourceTypes).to.exist;
});
describe('when configuring the type with class and attributes', () => {
beforeEach(() => {
compareCls = OtherResource;
config = {
Other: {
className: 'OtherResource',
attrTypes: {
attr: 'Other'
}
}
};
halResourceTypes.setResourceTypeConfig(config);
});
expectResourceClassAdded();
expectAttributeClassAdded();
});
describe('when configuring the type with the class name as value', () => {
beforeEach(() => {
compareCls = OtherResource;
config = {
Other: 'OtherResource'
};
halResourceTypes.setResourceTypeConfig(config);
});
expectResourceClassAdded();
});
describe('when configuring the type with only the attribute types', () => {
beforeEach(() => {
compareCls = halResourceFactory.defaultClass;
config = {
Other: {
attr: 'Other'
}
};
halResourceTypes.setResourceTypeConfig(config);
});
expectResourceClassAdded();
expectAttributeClassAdded();
});
describe('when an attribute has a type, that defined later in the config', () => {
beforeEach(() => {
compareCls = FooResource;
config = {
Other: {
attr: 'Foo'
},
Foo: 'FooResource',
};
halResourceTypes.setResourceTypeConfig(config);
});
expectAttributeClassAdded();
});
});

@ -1,85 +0,0 @@
// -- 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 {opApiModule} from '../../../../angular-modules';
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service';
export class HalResourceTypesService {
constructor(protected $injector:ng.auto.IInjectorService,
protected halResourceFactory:HalResourceFactoryService,
HalResource:any) {
halResourceFactory.defaultClass = HalResource;
}
public setResourceTypeConfig(config:any) {
const types = Object.keys(config).map(typeName => {
const value = config[typeName];
const result = {
typeName: typeName,
className: value.className || this.getClassName(this.halResourceFactory.defaultClass),
attrTypes: value.attrTypes || {}
};
if (angular.isString(value)) {
result.className = value;
}
if (!value.className && angular.isObject(value)) {
result.attrTypes = value;
}
return result;
});
types.forEach(typeConfig => {
this.halResourceFactory
.setResourceType(typeConfig.typeName, this.$injector.get(typeConfig.className) as any);
});
types
.forEach(typeConfig => {
this.halResourceFactory.setResourceTypeAttributes(typeConfig.typeName, typeConfig.attrTypes);
});
}
/**
* IE11 has no support for <Function>.name, thus polyfill the actual class name
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
* @param cls
*/
protected getClassName(cls:Function) {
if (cls.hasOwnProperty('name')) {
return cls.name;
}
const matches = cls.toString().match(/^function\s*([^\s(]+)/) as any[];
return matches[1];
}
}
opApiModule.service('halResourceTypes', HalResourceTypesService);

@ -1,74 +0,0 @@
// -- 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 {opApiModule, opServicesModule} from '../../../../angular-modules';
import {AttachmentCollectionResource} from './attachment-collection-resource.service';
import {OpenProjectFileUploadService} from '../../op-file-upload/op-file-upload.service';
describe('AttachmentCollectionResource service', () => {
var AttachmentCollectionResource:any;
var opFileUpload: OpenProjectFileUploadService;
beforeEach(angular.mock.module(
opApiModule.name,
opServicesModule.name
));
beforeEach(angular.mock.inject(function (_AttachmentCollectionResource_:any,
_opFileUpload_:any) {
[AttachmentCollectionResource, opFileUpload] = _.toArray(arguments);
}));
it('should exist', () => {
expect(AttachmentCollectionResource).to.exist;
});
describe('when creating an attachment collection', () => {
var collection: AttachmentCollectionResource;
beforeEach(() => {
collection = new AttachmentCollectionResource({
_links: {self: {href: 'attachments'}}
}, true);
});
describe('when using upload()', () => {
var uploadStub: sinon.SinonStub;
var params:any;
beforeEach(() => {
params = [{}, {}];
uploadStub = sinon.stub(opFileUpload, 'upload');
collection.upload((params));
});
it('should upload the files as expected', () => {
expect(uploadStub.calledWith(collection.$href, params)).to.be.true;
});
});
});
});

@ -1,60 +0,0 @@
//-- 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 {opApiModule} from '../../../../angular-modules';
import {CollectionResource} from './collection-resource.service';
import {HalResource} from './hal-resource.service';
import {
OpenProjectFileUploadService,
UploadFile, UploadResult
} from '../../op-file-upload/op-file-upload.service';
import IPromise = angular.IPromise;
var opFileUpload: OpenProjectFileUploadService;
export class AttachmentCollectionResource extends CollectionResource {
/**
* Upload the given files to the $href property of this resource.
*/
public upload(files: UploadFile[]): UploadResult {
return opFileUpload.upload(this.$href as string, files);
}
}
export interface AttachmentCollectionResourceInterface extends AttachmentCollectionResource {
elements: HalResource[];
}
function attachmentCollectionResourceService(...args:any[]) {
[opFileUpload] = args;
return AttachmentCollectionResource;
}
attachmentCollectionResourceService.$inject = ['opFileUpload'];
opApiModule.factory('AttachmentCollectionResource', attachmentCollectionResourceService);

@ -1,76 +0,0 @@
// -- 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 {opApiModule, opServicesModule} from '../../../../angular-modules';
import IQService = angular.IQService;
import IRootScopeService = angular.IRootScopeService;
describe('CollectionResource service', () => {
var $q:any;
var $rootScope:any;
var CollectionResource:any;
var source:any;
var collection:any;
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name));
beforeEach(angular.mock.inject(function (_$q_:any, _$rootScope_:any, _CollectionResource_:any) {
[$q, $rootScope, CollectionResource] = _.toArray(arguments);
}));
function createCollection() {
source = source || {};
collection = new CollectionResource(source);
}
it('should exist', () => {
expect(CollectionResource).to.exist;
});
describe('when using updateElements', () => {
var elements:any;
var result:any;
beforeEach(() => {
createCollection();
elements = [{}, {}];
sinon.stub(collection, '$load').returns($q.when({elements}));
result = collection.updateElements();
$rootScope.$apply();
});
it('should set the elements of the resource so the new value', () => {
expect(collection.elements).to.equal(elements);
});
it('should return a promise with the elements as the result', () => {
expect(result).to.eventually.equal(elements);
});
});
});

@ -1,55 +0,0 @@
//-- 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 {opApiModule} from '../../../../angular-modules';
import {HalResource} from './hal-resource.service';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
interface CustomActionResourceEmbedded {
}
export interface CustomActionResourceLinks extends CustomActionResourceEmbedded {
self():ng.IPromise<CustomActionResourceInterface>;
executeImmediately(payload:any):ng.IPromise<WorkPackageResourceInterface>;
}
export class CustomActionResource extends HalResource {
public $embedded:CustomActionResourceEmbedded;
public id:number;
public name:string;
public description:string;
}
function customActionResource() {
return CustomActionResource;
}
export interface CustomActionResourceInterface extends CustomActionResourceLinks, CustomActionResourceEmbedded, CustomActionResource {
}
opApiModule.factory('CustomActionResource', customActionResource);

@ -1,726 +0,0 @@
//-- 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 {opApiModule, opServicesModule} from '../../../../angular-modules';
import {HalResource} from './hal-resource.service';
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service';
import {HalRequestService} from '../hal-request/hal-request.service';
import {HalLinkInterface} from './../hal-link/hal-link.service';
describe('HalResource service', () => {
var $httpBackend:ng.IHttpBackendService;
var halResourceFactory:HalResourceFactoryService;
var resource:any;
var source:any;
class OtherResource extends HalResource {
}
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name, ($provide:ng.auto.IProvideService) => {
$provide.value('OtherResource', OtherResource);
}));
beforeEach(angular.mock.inject(function (_$httpBackend_:any,
_halResourceFactory_:any,
halRequest:HalRequestService) {
[$httpBackend, halResourceFactory] = _.toArray(arguments);
halRequest.defaultHeaders.caching.enabled = false;
}));
it('should exist', () => {
expect(HalResource).to.exist;
});
it('should be instantiable using a default object', () => {
expect(new HalResource().$href).to.equal(null);
});
describe('when updating a loaded resource using `$update()`', () => {
beforeEach(() => {
source = {
_links: {
self: {
href: 'hello'
}
}
};
resource = new HalResource(source);
resource.$update();
});
it('should perform a no-cache request', () => {
const expectHeaders = (headers:any) => headers.caching.enabled === false;
$httpBackend.expectGET('hello', expectHeaders).respond(200, {});
$httpBackend.flush();
});
});
describe('when creating a resource using the create factory method', () => {
describe('when there is no type configuration', () => {
beforeEach(() => {
source = {_embedded: {}};
resource = HalResource.create(source);
});
it('should be an instance of HalResource', () => {
expect(resource).to.be.an.instanceOf(HalResource);
});
});
describe('when the type is configured', () => {
beforeEach(() => {
source = {
_type: 'Other',
_links: {
someResource: {
href: 'foo'
}
}
};
halResourceFactory.setResourceType('Other', OtherResource);
halResourceFactory.setResourceTypeAttributes('Other', {
someResource: 'Other'
});
resource = HalResource.create(source);
});
it('should be an instance of that type', () => {
expect(resource).to.be.an.instanceOf(OtherResource);
});
it('should have an attribute that is of the configured instance', () => {
expect(resource.someResource).to.be.an.instanceOf(OtherResource);
});
it('should not be loaded', () => {
expect(resource.someResource.$loaded).to.be.false;
});
});
});
describe('when after generating the lazy object', () => {
var linkFn = sinon.spy();
var embeddedFn = sinon.spy();
beforeEach(() => {
resource = new HalResource({
_links: {
get link() {
linkFn();
return {};
}
},
_embedded: {
get resource() {
embeddedFn();
return {};
}
}
});
});
it('should not have touched the source links initially', () => {
expect(linkFn.called).to.be.false;
});
it('should not have touched the embedded elements of the source initially', () => {
expect(embeddedFn.called).to.be.false;
});
it('should use the source link only once when called', () => {
resource.link;
resource.link;
expect(linkFn.calledOnce).to.be.true;
});
it('should use the source embedded only once when called', () => {
resource.resource;
resource.resource;
expect(embeddedFn.calledOnce).to.be.true;
});
});
describe('when the source has properties, the resource', () => {
beforeEach(() => {
source = {
_links: {},
_embedded: {},
property: 'foo',
obj: {
foo: 'bar'
}
};
resource = new HalResource(source);
});
it('should have the same properties', () => {
expect(resource.property).to.exist;
expect(resource.obj).to.exist;
});
it('should have properties with equal values', () => {
expect(resource.property).to.eq(source.property);
expect(resource.obj).to.eql(source.obj);
});
it('should not have the _links property', () => {
expect(resource._links).to.not.exist;
});
it('should not have the _embedded property', () => {
expect(resource._embedded).to.not.exist;
});
it('should have enumerable properties', () => {
expect(resource.propertyIsEnumerable('property')).to.be.true;
});
describe('when a property is changed', () => {
beforeEach(() => {
resource.property = 'carrot';
});
it('should change the property of the source', () => {
expect(resource.$source.property).to.eq('carrot');
});
});
});
describe('when creating a resource from a source with a self link', () => {
beforeEach(() => {
source = {
_links: {
self: {
href: '/api/hello',
title: 'some title'
}
}
};
resource = new HalResource(source);
});
it('should have a name attribute that is equal to the title of the self link', () => {
expect(resource.name).to.eq('some title');
});
it('should have a writable name attribute', () => {
resource.name = 'some name';
expect(resource.name).to.eq('some name');
});
it('should have a href property that is the same as the self href', () => {
expect(resource.href).to.eq(resource.$links.self.$link.href);
});
it('should have a href property that is equal to the source href', () => {
expect(resource.href).to.eq(source._links.self.href);
});
it('should not have a self property', () => {
expect(resource.self).not.to.exist;
});
});
describe('when setting a property that is a resource to null', () => {
beforeEach(() => {
source = {
_links: {
resource: {
method: 'get',
href: 'resource/1'
}
}
};
resource = new HalResource(source);
resource.resource = null;
});
it('should be null', () => {
expect(resource.resource).to.be.null;
});
it('should set the respective link href to null', () => {
expect(resource.$source._links.resource.href).to.be.null;
});
});
describe('when a property that is a resource has a null href', () => {
beforeEach(() => {
source = {
_links: {
property: {
href: null
}
}
};
resource = new HalResource(source);
});
it('should be null', () => {
expect(resource.property).to.be.null;
});
});
describe('when using $plain', () => {
var plain:any;
beforeEach(() => {
source = {
_links: {self: {href: 'bunny'}},
rabbit: 'fluffy'
};
plain = new HalResource(source).$plain();
});
it('should return an object that is equal to the source', () => {
expect(plain).to.eql(source);
});
it('should not be the exact same object', () => {
expect(plain).not.to.equal(source);
});
});
describe('when creating a resource with a source that has no links', () => {
beforeEach(() => {
resource = new HalResource({});
});
it('should return null for the href if it has no self link', () => {
expect(resource.href).to.equal(null);
});
it('should have a $link object with null href', () => {
expect(resource.$link.href).to.equal(null);
});
});
describe('when creating a resource form a source with linked resources', () => {
beforeEach(() => {
source = {
_links: {
self: {
href: 'unicorn/69'
},
beaver: {
href: 'justin/420'
}
}
};
resource = new HalResource(source);
});
it('should have no "self" property', () => {
expect(resource.self).to.not.exist;
});
it('should have a beaver', () => {
expect(resource.beaver).to.exist;
});
it('should have no "_links" property', () => {
expect(resource._links).to.not.exist;
});
it('should leave the source accessible', () => {
expect(resource.$source).to.eql(source);
});
it('should have a callable self link', () => {
expect(() => resource.$links.self()).to.not.throw(Error);
});
it('should have a callable beaver', () => {
expect(() => resource.$links.beaver()).to.not.throw(Error);
});
it('should have a $links property with the keys of its source _links', () => {
const transformedLinks = Object.keys(resource.$links);
const plainLinks = Object.keys(source._links);
expect(transformedLinks).to.have.members(plainLinks);
});
});
describe('when creating a resource from a source with embedded resources', () => {
beforeEach(() => {
source = {
_embedded: {
resource: {_links: {}},
}
};
resource = new HalResource(source);
});
it('should not have the original _embedded property', () => {
expect(resource._embedded).to.not.be.ok;
});
it('should have a property, that is a loaded resource', () => {
expect(resource.resource.$loaded).to.be.true;
});
it('should have an embedded resource, that is loaded', () => {
expect(resource.$embedded.resource.$loaded).to.be.true;
});
it('should have a property that is the resource', () => {
expect(resource.resource).to.equal(resource.$embedded.resource);
});
it.skip('should have a callable link to that resource', () => {
expect(() => resource.$links.resource()).to.not.throw(Error);
});
describe('when overriding the property with a resource', () => {
var link:HalLinkInterface;
beforeEach(() => {
link = {
href: 'pony',
method: 'GET'
};
resource.resource = HalResource.fromLink(link);
});
it('should set the property to that resource', () => {
expect(resource.resource.href).to.equal(link.href);
});
it.skip('should set the corresponding link', () => {
expect(resource.$links.resource.$link.href).to.equal(link.href);
});
});
describe('when the embedded resources are nested', () => {
var first:any;
var deep:any;
beforeEach(() => {
source._embedded.resource._embedded = {
first: {
_embedded: {
second: {
_links: {},
property: 'yet another value'
}
},
property: 'another value'
}
};
first = resource.$embedded.resource.$embedded.first;
deep = resource.$embedded.resource.$embedded.first.$embedded.second;
});
it('should crate all nested resources recursively', () => {
expect(deep.$isHal).to.be.true;
});
it('should transfer the properties of the nested resources correctly', () => {
expect(first.property).to.eq('another value');
expect(deep.property).to.eq('yet another value');
});
});
});
describe('when creating a resource from a source with a linked array property', () => {
var expectLengthsToBe = (length:any, update = 'update') => {
it(`should ${update} the values of the resource`, () => {
expect(resource.values).to.have.lengthOf(length);
});
it(`should ${update} the source`, () => {
expect(source._links.values).to.have.lengthOf(length);
});
it(`should ${update} the $source property`, () => {
expect(resource.$source._links.values).to.have.lengthOf(length);
});
};
beforeEach(() => {
source = {
_links: {
values: [
{
href: '/api/value/1',
title: 'val1'
},
{
href: '/api/value/2',
title: 'val2'
}
]
}
};
resource = new HalResource(source);
});
it('should be an array that is a property of the resource', () => {
expect(resource).to.have.property('values').that.is.an('array');
});
expectLengthsToBe(2);
describe('when adding resources to the array', () => {
beforeEach(() => {
resource.values.push(resource);
});
expectLengthsToBe(3);
});
describe('when adding arbitrary values to the array', () => {
beforeEach(() => {
resource.values.push('something');
});
expectLengthsToBe(2, 'not update');
});
describe('when removing resources from the array', () => {
beforeEach(() => {
resource.values.pop();
});
expectLengthsToBe(1);
});
describe('when each value is transformed', () => {
beforeEach(() => {
resource = resource.values[0];
source = source._links.values[0];
});
it('should have made each link a resource', () => {
expect(resource.$isHal).to.be.true;
});
it('should be resources generated from the links', () => {
expect(resource.href).to.eq(source.href);
});
it('should have a name attribute equal to the title of its link', () => {
expect(resource.name).to.eq(source.title);
});
it('should not be loaded', () => {
expect(resource.$loaded).to.be.false;
});
});
});
describe('when transforming an object with an _embedded list with the list element having _links', () => {
beforeEach(() => {
source = {
_embedded: {
elements: [{_links: {}}, {_links: {}}]
}
};
resource = new HalResource(source);
});
it('should not have the original _embedded property', () => {
expect(resource._embedded).to.not.be.ok;
});
it('should transform the list elements', () => {
expect(resource.$embedded.elements[0].$isHal).to.be.true;
expect(resource.$embedded.elements[1].$isHal).to.be.true;
});
});
describe('when transforming an object with _links and _embedded', () => {
beforeEach(() => {
source = {
_links: {
property: {
href: '/api/property',
title: 'Property'
},
embedded: {
href: '/api/embedded',
},
action: {
href: '/api/action',
method: 'post'
},
self: {
href: '/api/self'
}
},
_embedded: {
embedded: {
_links: {
self: {
href: '/api/embedded'
}
},
name: 'name'
},
notLinked: {
_links: {
self: {
href: '/api/not-linked'
}
}
}
}
};
resource = new HalResource(source);
});
it('should be loaded', () => {
expect(resource.$loaded).to.be.true;
});
it('should not be possible to override a link', () => {
try {
resource.$links.action = 'foo';
} catch (ignore) {
/**/
}
expect(resource.$links.action).to.not.eq('foo');
});
it('should not be possible to override an embedded resource', () => {
try {
resource.$embedded.embedded = 'foo';
} catch (ignore) {
/**/
}
expect(resource.$embedded.embedded).to.not.eq('foo');
});
it('should have linked resources as properties', () => {
expect(resource.property).to.exist;
});
it('should have linked actions as properties', () => {
expect(resource.action).to.exist;
});
it('should have embedded resources as properties', () => {
expect(resource.embedded).to.exist;
});
it('should have embedded, but not linked resources as properties', () => {
expect(resource.notLinked).to.exist;
});
describe('when a resource that is linked and embedded is updated', () => {
var embeddedResource;
beforeEach(() => {
embeddedResource = {
$link: {
method: 'get',
href: 'newHref'
}
};
resource.embedded = embeddedResource;
});
it('should update the source', () => {
expect(resource.$source._links.embedded.href).to.eq('newHref');
});
});
describe('when after generating the properties from the links, each property', () => {
it('should be a function, if the link method is not "get"', () => {
expect(resource).to.respondTo('action');
});
it('should be a resource, if the link method is "get"', () => {
expect(resource.property.$isHal).to.be.true;
});
describe('when a property is a resource', () => {
it('should not be callable', () => {
expect(resource).to.not.to.respondTo('property');
});
it('should not be loaded initially', () => {
expect(resource.property.$loaded).to.be.false;
expect(resource.notLinked.$loaded).to.be.true;
});
it('should be loaded, if the resource is embedded', () => {
expect(resource.embedded.$loaded).to.be.true;
});
it('should update the source when set', () => {
resource.property = resource;
expect(resource.$source._links.property.href).to.eql('/api/self');
});
describe('when loading it', () => {
beforeEach(() => {
resource = resource.property;
resource.$load();
$httpBackend.expectGET('/api/property').respond(200, {
_links: {},
name: 'name',
foo: 'bar'
});
$httpBackend.flush();
});
it('should be loaded', () => {
expect(resource.$loaded).to.be.true;
});
it('should be updated', () => {
expect(resource.name).to.eq('name');
});
it('should have properties that have a getter', () => {
expect((Object as any).getOwnPropertyDescriptor(resource, 'foo').get).to.exist;
});
it('should have properties that have a setter', () => {
expect((Object as any).getOwnPropertyDescriptor(resource, 'foo').set).to.exist;
});
it('should return itself in a promise if already loaded', () => {
resource.$loaded = 1;
expect(resource.$load()).to.eventually.be.fulfilled.then(result => {
expect(result).to.equal(result);
});
});
});
});
});
});
});

@ -1,394 +0,0 @@
//-- 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 {InputState} from "reactivestates";
import {opApiModule} from "../../../../angular-modules";
import {HalLink, HalLinkInterface} from "../hal-link/hal-link.service";
import {HalResourceFactoryService} from "../hal-resource-factory/hal-resource-factory.service";
const ObservableArray:any = require('observable-array');
var $q:ng.IQService;
var lazy:Function;
var halLink:typeof HalLink;
var halResourceFactory:HalResourceFactoryService;
var CacheService:any;
export class HalResource {
[attribute:string]:any;
public _type:string;
public static create(element:any, force:boolean = false) {
if (_.isNil(element)) {
return element;
}
if (!force && !(element._embedded || element._links)) {
return element;
}
return halResourceFactory.createHalResource(element);
}
public static fromLink(link:HalLinkInterface) {
const resource = HalResource.getEmptyResource(halLink.fromObject(link));
return new HalResource(resource, false);
}
public static getEmptyResource(self:{href:string|null} = {href: null}):any {
return {_links: {self: self}};
}
public $links:any = {};
public $embedded:any = {};
public $self:ng.IPromise<this>;
public _name:string;
public static idFromLink(href:string):string {
return href.split('/').pop()!;
}
public get idFromLink():string {
if (this.$href) {
return HalResource.idFromLink(this.$href);
}
return '';
}
public get $isHal():boolean {
return true;
}
public get $link():HalLinkInterface {
return this.$links.self.$link;
}
public get name():string {
return this._name || this.$link.title || '';
}
public set name(name:string) {
this._name = name;
}
/**
* Alias for $href.
* Please use $href instead.
*
* @deprecated
*/
public get href():string|null {
return this.$link.href;
}
public get $href():string|null {
return this.$link.href;
}
constructor(public $source:any = HalResource.getEmptyResource(),
public $loaded:boolean = true) {
this.$initialize($source);
}
/**
* Return the associated state to this HAL resource, if any.
*/
public get state(): InputState<this> | null {
return null;
}
public $load(force = false):ng.IPromise<this> {
if (!this.state) {
return this.$loadResource(force);
}
const state = this.state;
if (force) {
state.clear();
}
// If nobody has asked yet for the resource to be $loaded, do it ourselves.
// Otherwise, we risk returning a promise, that will never be resolved.
state.putFromPromiseIfPristine(() => this.$loadResource(force));
return <ng.IPromise<this>> state.valuesPromise().then(source => {
this.$initialize(source);
this.$loaded = true;
return this;
});
}
protected $loadResource(force = false):ng.IPromise<this> {
if (!force) {
if (this.$loaded) {
return $q.when(this);
}
if (!this.$loaded && this.$self) {
return this.$self;
}
}
// HACK: Remove cleared promise key from cache.
// We should not be so clever as to do that, instead, rewrite this with states.
if (force) {
CacheService.clearPromisedKey(this.$links.self.href);
}
// Reset and load this resource
this.$loaded = false;
this.$self = this.$links.self({}, this.$loadHeaders(force)).then((source:any) => {
this.$loaded = true;
this.$initialize(source);
return this;
});
return this.$self;
}
/**
* Update the resource ignoring the cache.
*/
public $update() {
return this.$load(true);
}
public $plain() {
return angular.copy(this.$source);
}
public $copy() {
let clone:any = this.constructor
return new clone(_.cloneDeep(this.$source), this.$loaded);;
}
public $initialize(source:any) {
this.$source = source.$source || source;
initializeResource(this);
}
/**
* $load by default uses the $http cache. This will likely be replaced by
the HAL cache, but while it lasts, it should be ignored when using
force.
*/
protected $loadHeaders(force:boolean) {
var headers:any = {};
if (force) {
headers.caching = {enabled: false};
}
return headers;
}
/**
* Specify this resource's embedded keys that should be transformed with resources.
* Use this to restrict, e.g., links that should not be made properties if you have a custom get/setter.
*/
public $embeddableKeys():string[] {
const properties = Object.keys(this.$source);
return _.without(properties, '_links', '_embedded');
}
/**
* Specify this resource's keys that should not be transformed with resources.
* Use this to restrict, e.g., links that should not be made properties if you have a custom get/setter.
*/
public $linkableKeys():string[] {
const properties = Object.keys(this.$links);
return _.without(properties, 'self');
}
/**
* Get a linked resource from its HalLink with the correct ype
*/
public createLinkedResource(linkName:string, link:HalLinkInterface) {
const resource = HalResource.getEmptyResource();
const type = this.constructor._type;
resource._links.self = link;
return halResourceFactory.createLinkedHalResource(resource, type, linkName);
}
}
function initializeResource(halResource:HalResource) {
setSource();
setupLinks();
setupEmbedded();
proxyProperties();
setLinksAsProperties();
setEmbeddedAsProperties();
function setSource() {
if (!halResource.$source._links) {
halResource.$source._links = {};
}
if (!halResource.$source._links.self) {
halResource.$source._links.self = new HalLink();
}
}
function proxyProperties() {
halResource.$embeddableKeys().forEach((property:any) => {
Object.defineProperty(halResource, property, {
get() {
return halResource.$source[property];
},
set(value) {
halResource.$source[property] = value;
},
enumerable: true,
configurable: true
});
});
}
function setLinksAsProperties() {
halResource.$linkableKeys().forEach((linkName:string) => {
lazy(halResource, linkName,
() => {
const link:any = halResource.$links[linkName].$link || halResource.$links[linkName];
if (Array.isArray(link)) {
var items = link.map(item => halResource.createLinkedResource(linkName, item.$link));
var property:HalResource[] = new ObservableArray(...items).on('change', () => {
property.forEach(item => {
if (!item.$link) {
property.splice(property.indexOf(item), 1);
}
});
halResource.$source._links[linkName] = property.map(item => item.$link);
});
return property;
}
if (link.href) {
if (link.method !== 'get') {
return HalLink.callable(link);
}
return halResource.createLinkedResource(linkName, link);
}
return null;
},
(val:any) => setter(val, linkName)
);
});
}
function setEmbeddedAsProperties() {
if (!halResource.$source._embedded) {
return;
}
Object.keys(halResource.$source._embedded).forEach(name => {
lazy(halResource, name, () => halResource.$embedded[name], (val:any) => setter(val, name));
});
}
function setupProperty(name:string, callback:(element:any) => any) {
const instanceName = '$' + name;
const sourceName = '_' + name;
const sourceObj = halResource.$source[sourceName];
if (angular.isObject(sourceObj)) {
Object.keys(sourceObj).forEach(propName => {
lazy((halResource as any)[instanceName], propName, () => callback(sourceObj[propName]));
});
}
}
function setupLinks() {
setupProperty('links',
link => Array.isArray(link) ? link.map(HalLink.callable) : HalLink.callable(link));
}
function setupEmbedded() {
setupProperty('embedded', element => {
angular.forEach(element, (child:any, name:string) => {
if (child && (child._embedded || child._links)) {
lazy(element, name, () => HalResource.create(child));
}
});
if (Array.isArray(element)) {
return element.map((source) => HalResource.create(source, true));
}
return HalResource.create(element);
});
}
function setter(val:HalResource|{ href?: string }, linkName:string) {
if (!val) {
halResource.$source._links[linkName] = {href: null};
} else if (_.isArray(val)) {
halResource.$source._links[linkName] = val.map((el:any) => { return {href: el.href} });
} else if (val.hasOwnProperty('$link')) {
const link = (val as HalResource).$link;
if (link.href) {
halResource.$source._links[linkName] = link;
}
} else if ('href' in val) {
halResource.$source._links[linkName] = {href: val.href};
}
if (halResource.$embedded && halResource.$embedded[linkName]) {
halResource.$embedded[linkName] = val;
halResource.$source._embedded[linkName] = _.get(val, '$source', val);
}
return val;
}
}
function halResourceService(...args:any[]) {
[$q, lazy, halLink, halResourceFactory, CacheService] = args;
return HalResource;
}
halResourceService.$inject = [
'$q',
'lazy',
'HalLink',
'halResourceFactory',
'CacheService'
];
opApiModule.factory('HalResource', halResourceService);

@ -1,41 +0,0 @@
//-- 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 {SchemaResource, SchemaAttributeObject} from './schema-resource.service';
import {CollectionResource} from './collection-resource.service';
import {QueryColumn} from '../../../wp-query/query-column';
import {QuerySortByResource} from './query-sort-by-resource.service';
import {QueryGroupByResource} from './query-group-by-resource.service';
import {QueryFilterInstanceSchemaResource} from "core-components/api/api-v3/hal-resources/query-filter-instance-schema-resource.service";
export interface QuerySchemaResourceInterface extends SchemaResource {
columns:{ allowedValues: QueryColumn[] };
filtersSchemas: CollectionResource<QueryFilterInstanceSchemaResource>;
sortBy:{ allowedValues: QuerySortByResource[] };
groupBy:{ allowedValues: QueryGroupByResource[] };
}

@ -1,455 +0,0 @@
//-- 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 {opApiModule} from '../../../../angular-modules';
import {WorkPackageCacheService} from '../../../work-packages/work-package-cache.service';
import IHttpBackendService = angular.IHttpBackendService;
import IQService = angular.IQService;
import IRootScopeService = angular.IRootScopeService;
import IPromise = angular.IPromise;
describe('WorkPackageResource service', () => {
var $httpBackend: IHttpBackendService;
var $rootScope: IRootScopeService;
var $q: IQService;
var WorkPackageResource:any;
var AttachmentCollectionResource:any;
var wpCacheService: WorkPackageCacheService;
var NotificationsService: any;
var wpNotificationsService: any;
var source: any;
var workPackage: any;
const createWorkPackage = () => {
source = source || { id: 'new' };
workPackage = new WorkPackageResource(source);
};
const expectUncachedRequest = (href:string) => {
$httpBackend
.expectGET(href, (headers:any) => headers.caching.enabled === false)
.respond(200, {_links: {self: {href}}});
};
const expectUncachedRequests = (...urls:string[]) => {
urls.forEach(expectUncachedRequest);
$httpBackend.flush();
};
beforeEach(angular.mock.module(opApiModule.name));
beforeEach(angular.mock.inject(function (_$httpBackend_:any,
_$rootScope_:any,
_$q_:any,
_WorkPackageResource_:any,
_AttachmentCollectionResource_:any,
_wpCacheService_:any,
_NotificationsService_:any,
_wpNotificationsService_:any) {
[
$httpBackend,
$rootScope,
$q,
WorkPackageResource,
AttachmentCollectionResource,
wpCacheService,
NotificationsService,
wpNotificationsService
] = _.toArray(arguments);
}));
it('should exist', () => {
expect(WorkPackageResource).to.exist;
});
describe('when creating an empty work package', () => {
beforeEach(createWorkPackage);
it('should have an attachments property of type `AttachmentCollectionResource`', () => {
expect(workPackage.attachments).to.be.instanceOf(AttachmentCollectionResource);
});
it('should return true for `isNew`', () => {
expect(workPackage.isNew).to.be.true;
});
});
describe('when retrieving `canAddAttachment`', () => {
beforeEach(createWorkPackage);
const expectValue = (value:any, prepare = () => angular.noop()) => {
value = value.toString();
beforeEach(prepare);
it('should be ' + value, () => {
(expect(workPackage.canAddAttachments).to.be as any)[value];
});
};
describe('when the work package is new', () => {
expectValue(true);
});
describe('when the work package is not new', () => {
expectValue(false, () => {
workPackage.id = 420;
});
});
describe('when the work work package has no `addAttachment` link and is not new', () => {
expectValue(false, () => {
workPackage.id = 69;
workPackage.$links.addAttachment = null;
});
});
describe('when the work work package has an `addAttachment` link', () => {
expectValue(true, () => {
workPackage.$links.addAttachment = <any> angular.noop;
});
});
});
describe('when updating multiple linked resources', () => {
var updateWorkPackageStub: sinon.SinonStub;
var result:Promise<any>;
const expectCacheUpdate = () => {
it('should update the work package cache', () => {
result.then(() => {
expect(updateWorkPackageStub.calledWith(workPackage.id)).to.be.true;
});
});
};
beforeEach(() => {
updateWorkPackageStub = sinon.stub(wpCacheService, 'touch');
});
afterEach(() => {
$rootScope.$apply();
updateWorkPackageStub.restore();
});
describe('when the resources are properties of the work package', () => {
const testResultIsResource = (href:string, prepare:any) => {
beforeEach(prepare);
expectCacheUpdate();
it('should be a promise with a resource where the $href is ' + href, () => {
expect(result).to.eventually.have.property('$href', href);
});
};
beforeEach(() => {
source = {
_links: {
schema: { _type: 'Schema', href: 'schema' },
attachments: { href: 'attachmentsHref' },
activities: { href: 'activitiesHref' }
}
};
createWorkPackage();
});
describe('when updating the properties using updateLinkedResources()', () => {
var results:any;
beforeEach(() => {
results = workPackage.updateLinkedResources('attachments', 'activities');
expectUncachedRequests('attachmentsHref', 'activitiesHref');
});
it('should return a result, that has the same properties as the updated ones', () => {
expect(results).to.eventually.be.fulfilled.then(results => {
expect(Object.keys(results)).to.have.members(['attachments', 'activities']);
});
});
testResultIsResource('attachmentsHref', () => {
results.then((results:any) => result = $q.when(results.attachments));
});
testResultIsResource('activitiesHref', () => {
results.then((results:any) => result = $q.when(results.activities));
});
});
describe('when updating the activities', () => {
testResultIsResource('activitiesHref', () => {
result = workPackage.updateActivities();
expectUncachedRequests('activitiesHref');
});
});
describe('when updating the attachments', () => {
testResultIsResource('attachmentsHref', () => {
result = workPackage.updateAttachments();
expectUncachedRequests('activitiesHref', 'attachmentsHref');
});
});
});
describe('when the linked resource are not properties of the work package', () => {
const expectRejectedWithCacheUpdate = (prepare:any) => {
beforeEach(prepare);
it('should return a rejected promise', () => {
expect(result).to.eventually.be.rejected;
});
expectCacheUpdate();
};
beforeEach(() => {
source = {};
createWorkPackage();
});
describe('when using updateLinkedResources', () => {
expectRejectedWithCacheUpdate(() => {
result = workPackage.updateLinkedResources('attachments', 'activities');
});
});
describe('when using updateActivities', () => {
expectRejectedWithCacheUpdate(() => {
result = workPackage.updateActivities();
});
});
describe('when using updateAttachments', () => {
expectRejectedWithCacheUpdate(() => {
result = workPackage.updateAttachments();
});
});
});
});
describe('when a work package is created with attachments and activities', () => {
beforeEach(() => {
source = {
_links: {
schema: { _type: 'Schema', href: 'schema' },
attachments: { href: 'attachments' },
activities: { href: 'activities' }
},
isNew: true
};
createWorkPackage();
});
describe('when adding multiple attachments to the work package', () => {
var file: any = {};
var files: any[] = [file, file];
var uploadFilesDeferred:any;
var uploadAttachmentsPromise:any;
var attachmentsUploadStub:any;
var uploadNotificationStub:any;
beforeEach(() => {
uploadFilesDeferred = $q.defer();
const uploadResult = {
uploads: [uploadFilesDeferred.promise],
finished: uploadFilesDeferred.promise
};
attachmentsUploadStub = sinon.stub(workPackage.attachments, 'upload').returns(uploadResult);
uploadNotificationStub = sinon.stub(NotificationsService, 'addWorkPackageUpload');
uploadAttachmentsPromise = workPackage.uploadAttachments(files);
});
it('should call the upload method of the attachment collection resource', () => {
expect(attachmentsUploadStub.calledWith(files)).to.be.true;
});
it('should add an upload notification', () => {
expect(uploadNotificationStub.calledOnce).to.be.true;
});
describe('when the upload fails', () => {
var notificationStub:any;
var error = 'err';
beforeEach(() => {
uploadFilesDeferred.reject(error);
notificationStub = sinon.stub(wpNotificationsService, 'handleRawError');
$rootScope.$apply();
});
it('should call the error response notification', () => {
expect(notificationStub.calledWith(error, workPackage)).to.be.true;
});
});
describe('when the upload succeeds', () => {
var removeStub:any;
var updateWorkPackageStub:any;
var updateWorkPackageTouchStub: sinon.SinonStub;
beforeEach(() => {
updateWorkPackageStub = sinon.stub(wpCacheService, 'updateWorkPackage');
uploadFilesDeferred.resolve();
updateWorkPackageTouchStub = sinon.stub(wpCacheService, 'touch');
removeStub = sinon.stub(NotificationsService, 'remove');
expectUncachedRequest('activities');
expectUncachedRequest('attachments');
$httpBackend
.when('GET', 'schema')
.respond(200, {_links: {self: 'schema'}});
$httpBackend.flush();
$rootScope.$apply();
});
it('should remove the upload notification', angular.mock.inject(($timeout:ng.ITimeoutService) => {
$timeout.flush();
expect(removeStub.calledOnce).to.be.true;
}));
it('should return an attachment collection resource promise', () => {
expect(uploadAttachmentsPromise).to.eventually.have.property('$href', 'attachments');
$rootScope.$apply();
});
afterEach(() => {
updateWorkPackageStub.restore();
updateWorkPackageTouchStub.restore();
removeStub.restore();
});
});
});
describe('when using uploadPendingAttachments', () => {
var uploadAttachmentsStub: sinon.SinonStub;
beforeEach(() => {
workPackage.pendingAttachments.push({}, {});
uploadAttachmentsStub = sinon
.stub(workPackage, 'uploadAttachments')
.returns($q.when());
});
beforeEach(() => {
workPackage.isNew = false;
workPackage.uploadPendingAttachments();
});
it('should call the uploadAttachments method with the pendingAttachments', () => {
expect(uploadAttachmentsStub.calledWith([{},{}])).to.be.true;
});
describe('when the upload succeeds', () => {
beforeEach(() => {
$rootScope.$apply();
});
it('should reset the pending attachments', () => {
expect(workPackage.pendingAttachments).to.have.length(0);
});
});
});
});
describe('when using removeAttachment', () => {
var file:any;
var attachment:any;
beforeEach(() => {
file = {};
attachment = {
$isHal: true,
'delete': sinon.stub()
};
createWorkPackage();
workPackage.attachments = {elements: [attachment]};
workPackage.pendingAttachments.push(file);
});
describe('when the attachment is a regular file', () => {
beforeEach(() => {
workPackage.removeAttachment(file);
});
it('should be removed from the pending attachments', () => {
expect(workPackage.pendingAttachments).to.have.length(0);
});
});
describe('when the attachment is an attachment resource', () => {
var deletion:any;
beforeEach(() => {
deletion = $q.defer();
attachment.delete.returns(deletion.promise);
sinon.stub(workPackage, 'updateAttachments');
workPackage.removeAttachment(attachment);
});
it('should call its delete method', () => {
expect(attachment.delete.calledOnce).to.be.true;
});
describe('when the deletion gets resolved', () => {
beforeEach(() => {
deletion.resolve();
$rootScope.$apply();
});
it('should call updateAttachments()', () => {
expect(workPackage.updateAttachments.calledOnce).to.be.true;
});
});
describe('when an error occurs', () => {
var error:any;
beforeEach(() => {
error = {foo: 'bar'};
sinon.stub(wpNotificationsService, 'handleErrorResponse');
deletion.reject(error);
$rootScope.$apply();
});
it('should call the handleErrorResponse notification', () => {
const calledWithErrorAndWorkPackage = wpNotificationsService
.handleErrorResponse
.calledWith(error, workPackage);
expect(calledWithErrorAndWorkPackage).to.be.true;
});
it('should not remove the attachment from the elements array', () => {
expect(workPackage.attachments.elements).to.have.length(1);
});
});
});
});
});

@ -26,21 +26,13 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {HalResource} from '../api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {opApiModule} from '../../../angular-modules';
import {HalRequestService} from '../api-v3/hal-request/hal-request.service';
import {
WorkPackageResource,
WorkPackageResourceInterface,
} from '../api-v3/hal-resources/work-package-resource.service';
import {
WorkPackageCollectionResource,
WorkPackageCollectionResourceInterface,
} from '../api-v3/hal-resources/wp-collection-resource.service';
import {States} from '../../states.service';
import IPromise = angular.IPromise;
import {SchemaResource} from '../api-v3/hal-resources/schema-resource.service';
import {ApiV3FilterBuilder, buildApiV3Filter} from '../api-v3/api-v3-filter-builder';
import {buildApiV3Filter} from '../api-v3/api-v3-filter-builder';
import {HalRequestService} from 'core-app/modules/hal/services/hal-request.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource';
export class ApiWorkPackagesService {
constructor(protected $q:ng.IQService,
@ -59,11 +51,7 @@ export class ApiWorkPackagesService {
public async loadWorkPackageById(id:string, force = false) {
const url = this.v3Path.wp({wp: id});
return this.halRequest.get(url, null, {
caching: {
enabled: !force
}
}) as Promise<WorkPackageResourceInterface>;
return this.halRequest.get<WorkPackageResource>(url).toPromise();
}
/**
@ -71,18 +59,16 @@ export class ApiWorkPackagesService {
* Returns a WP Collection with schemas and results embedded.
*
* @param ids
* @return {WorkPackageCollectionResourceInterface[]}
* @return {WorkPackageCollectionResource[]}
*/
public loadWorkPackagesCollectionsFor(ids:string[]) {
public loadWorkPackagesCollectionsFor(ids:string[]):Promise<WorkPackageCollectionResource[]> {
return this.halRequest.getAllPaginated(
this.v3Path.wps(),
ids.length,
{
filters: buildApiV3Filter('id', '=', ids).toJson(),
},
{
caching: { enabled: false }
}) as any;
}
) as any; // WorkPackageCollectionResource does not satisfy constraint HalResource[]
}
/**
@ -90,8 +76,10 @@ export class ApiWorkPackagesService {
*
* @returns An empty work package form resource.
*/
public emptyCreateForm(request:any, projectIdentifier?:string):ng.IPromise<HalResource> {
return this.halRequest.post(this.v3Path.wp.form({ project: projectIdentifier }), request);
public emptyCreateForm(request:any, projectIdentifier?:string):Promise<HalResource> {
return this.halRequest
.post<HalResource>(this.v3Path.wp.form({ project: projectIdentifier }), request)
.toPromise();
}
/**
@ -102,29 +90,35 @@ export class ApiWorkPackagesService {
* @param projectIdentifier: The project to which the work package is initialized
* @returns An empty work package form resource.
*/
public typedCreateForm(typeId:number, projectIdentifier?:string):ng.IPromise<HalResource> {
public typedCreateForm(typeId:number, projectIdentifier?:string):Promise<HalResource> {
const typeUrl = this.v3Path.types({type: typeId});
const request = { _links: { type: { href: typeUrl } } };
return this.halRequest.post(this.v3Path.wp.form({ project: projectIdentifier }), request);
return this.halRequest
.post<HalResource>(this.v3Path.wp.form({ project: projectIdentifier }), request)
.toPromise();
}
/**
* Returns a promise to GET `/api/v3/work_packages/available_projects`.
*/
public availableProjects(projectIdentifier?:string):ng.IPromise<HalResource> {
return this.halRequest.get(this.v3Path.wp.availableProjects({project: projectIdentifier}));
public availableProjects(projectIdentifier?:string):Promise<HalResource> {
return this.halRequest
.get<HalResource>(this.v3Path.wp.availableProjects({project: projectIdentifier}))
.toPromise();
}
/**
* Create a work package from a form payload
*
* @param payload
* @return {ng.IPromise<WorkPackageResource>}
* @return {Promise<WorkPackageResource>}
*/
public createWorkPackage(payload:any):ng.IPromise<WorkPackageResource> {
return this.halRequest.post(this.v3Path.wps(), payload);
public createWorkPackage(payload:any):Promise<WorkPackageResource> {
return this.halRequest
.post<WorkPackageResource>(this.v3Path.wps(), payload)
.toPromise();
}
}

@ -1,141 +0,0 @@
//-- 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.
//++
function CacheService($q:ng.IQService, CacheFactory:any) {
// Temporary storage for currently resolving promises
var _promises:{[key:string]: ng.IPromise<any>} = {};
// Global switch to disable all caches
var disabled = false;
var CacheService = {
temporaryCache: function() {
return CacheService.customCache('openproject-session_cache', {
maxAge: 10 * 60 * 1000, // 10 mins
storageMode: 'sessionStorage'
});
},
localStorage: function() {
return CacheService.customCache('openproject-local_storage_cache', {
storageMode: 'localStorage'
});
},
memoryStorage: function() {
return CacheService.customCache('openproject-memory_storage_cache', {
storageMode: 'memory'
});
},
customCache: function(identifier:string, params:any) {
var _cache = CacheFactory.get(identifier);
if (!_cache) {
_cache = CacheFactory(identifier, params);
}
if (disabled) {
_cache.disable();
}
return _cache;
},
isCacheDisabled: function() {
return disabled;
},
enableCaching: function() {
disabled = false;
},
disableCaching: function() {
disabled = true;
},
clearPromisedKey: function(key:string, options:any) {
options = options || {};
var cache = options.cache || CacheService.memoryStorage();
cache.remove(key);
},
cachedPromise: function(promiseFn:() => ng.IPromise<any>, key:string, options:any) {
options = options || {};
var cache = options.cache || CacheService.memoryStorage();
var force = options.force || false;
var deferred = $q.defer();
var cachedValue, promise;
// Return early when frontend caching is not desired
if (cache.disabled) {
return promiseFn();
}
// Got the result directly? Great.
cachedValue = cache.get(key);
if (cachedValue && !force) {
deferred.resolve(cachedValue);
return deferred.promise;
}
// Return an existing promise if it exists
// Avoids intermittent requests while a first
// is already underway.
if (_promises[key]) {
return _promises[key];
}
// Call now to retrieve promise
promise = promiseFn();
promise
.then(data => {
cache.put(key, data);
deferred.resolve(data);
})
.catch(error => {
deferred.reject(error);
cache.remove(key);
})
.finally(() => delete _promises[key]);
_promises[key] = deferred.promise;
return deferred.promise;
},
};
return CacheService;
}
angular
.module('openproject.services')
.factory('CacheService', CacheService);

@ -29,7 +29,7 @@
import {Component, Inject, Input, OnInit} from '@angular/core';
import {I18nToken, TimezoneServiceToken} from '../../../angular4-transition-utils';
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {opUiComponentsModule} from 'core-app/angular-modules';
import {downgradeComponent} from '@angular/upgrade/static';

@ -28,7 +28,7 @@
import {Component, Directive, ElementRef, Injector, Input, Output} from '@angular/core';
import {UpgradeComponent} from '@angular/upgrade/static';
import {UserResource} from 'core-components/api/api-v3/hal-resources/user-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
@Directive({
selector: 'op-date-time'

@ -30,9 +30,9 @@ import IAugmentedJQuery = angular.IAugmentedJQuery;
import {IDialogService} from 'ng-dialog';
import {IDialogScope} from 'ng-dialog';
import {opUiComponentsModule} from '../../../angular-modules';
import {HelpTextResourceInterface} from '../../api/api-v3/hal-resources/help-text-resource.service';
import {HelpTextDmService} from '../../api/api-v3/hal-resource-dms/help-text-dm.service';
import {AttributeHelpTextsService} from './attribute-help-text.service';
import {HelpTextDmService} from 'core-app/modules/dm-services/help-text-dm.service';
import {HelpTextResource} from 'core-app/modules/hal/resources/help-text-resource';
export class AttributeHelpTextController {
// Attribute to show help text for
@ -100,7 +100,7 @@ export class AttributeHelpTextController {
}
}
private renderModal(resource:HelpTextResourceInterface) {
private renderModal(resource:HelpTextResource) {
this.$scope.resource = resource;
this.ngDialog.open({
closeByEscape: true,

@ -26,14 +26,15 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {opUiComponentsModule} from '../../../angular-modules';
import {HelpTextResourceInterface} from '../../api/api-v3/hal-resources/help-text-resource.service';
import {HelpTextDmService} from '../../api/api-v3/hal-resource-dms/help-text-dm.service';
import {input} from 'reactivestates';
import {CollectionResource} from "core-components/api/api-v3/hal-resources/collection-resource.service";
import {HelpTextResource} from 'core-app/modules/hal/resources/help-text-resource';
import {HelpTextDmService} from 'core-app/modules/dm-services/help-text-dm.service';
import {Injectable} from '@angular/core';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
@Injectable()
export class AttributeHelpTextsService {
private helpTexts = input<HelpTextResourceInterface[]>();
private helpTexts = input<HelpTextResource[]>();
constructor(private helpTextDm:HelpTextDmService,
private $q:ng.IQService) {
@ -45,12 +46,12 @@ export class AttributeHelpTextsService {
* @param attribute
* @param scope
*/
public require(attribute:string, scope:string):ng.IPromise<HelpTextResourceInterface|undefined> {
const deferred = this.$q.defer<HelpTextResourceInterface|undefined>();
public require(attribute:string, scope:string):Promise<HelpTextResource|undefined> {
const deferred = this.$q.defer<HelpTextResource|undefined>();
if (this.helpTexts.isPristine()) {
this.helpTextDm.loadAll()
.then((resources:CollectionResource<HelpTextResourceInterface>) => {
.then((resources:CollectionResource<HelpTextResource>) => {
this.helpTexts.putValue(resources.elements as any);
deferred.resolve(this.find(attribute, scope));
});
@ -61,10 +62,8 @@ export class AttributeHelpTextsService {
return deferred.promise;
}
private find(attribute:string, scope:string):HelpTextResourceInterface|undefined {
private find(attribute:string, scope:string):HelpTextResource|undefined {
const value = this.helpTexts.getValueOr([]);
return _.find(value, (element) => element.scope === scope && element.attribute === attribute);
}
}
opUiComponentsModule.service('attributeHelpTexts', AttributeHelpTextsService);

@ -27,16 +27,16 @@
//++
import {PathHelperService} from './../path-helper/path-helper.service';
import {HalLink} from './../../api/api-v3/hal-link/hal-link.service';
import {WorkPackageResourceInterface} from 'app/components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageNotificationService} from './../../wp-edit/wp-notification.service';
import {HalLink} from 'core-app/modules/hal/hal-link/hal-link';
export class TextileService {
constructor(public $http:ng.IHttpService,
public wpNotificationsService:WorkPackageNotificationService,
public PathHelper:PathHelperService) {}
public renderWithWorkPackageContext(workPackage:WorkPackageResourceInterface, text:string) {
public renderWithWorkPackageContext(workPackage:WorkPackageResource, text:string) {
return this.render(workPackage.previewMarkup, text);
}

@ -29,7 +29,7 @@
import {filtersModule} from '../../../angular-modules';
import {Moment} from 'moment';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export abstract class AbstractDateTimeValueController {
public filter:QueryFilterInstanceResource;

@ -27,7 +27,7 @@
//++
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export class BooleanValueController {
public filter:QueryFilterInstanceResource;

@ -28,7 +28,7 @@
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {AbstractDateTimeValueController} from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'
export class DateTimeValueController extends AbstractDateTimeValueController {

@ -28,7 +28,7 @@
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {AbstractDateTimeValueController} from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'
export class DateTimesValueController extends AbstractDateTimeValueController {

@ -28,7 +28,7 @@
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export class DateValueController {
public filter:QueryFilterInstanceResource;

@ -28,7 +28,7 @@
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export class DatesValueController {
public filter:QueryFilterInstanceResource;

@ -28,9 +28,9 @@
import {filtersModule} from '../../../angular-modules';
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {QueryFilterResource} from '../../api/api-v3/hal-resources/query-filter-resource.service';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {QueryFilterResource} from 'core-app/modules/hal/resources/query-filter-resource';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export class IntegerValueController {
public filter:QueryFilterInstanceResource;

@ -27,7 +27,7 @@
//++
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
export class StringValueController {
public filter:QueryFilterInstanceResource;

@ -29,7 +29,7 @@
/*jshint expr: true*/
import {ToggledMultiselectController} from './filter-toggled-multiselect-value.directive'
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
describe('toggledMultiselect Directive', function() {
var compile:any, element:any, rootScope:any, scope:any, I18n:any;

@ -26,18 +26,16 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {filtersModule} from '../../../angular-modules';
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {UserResource} from '../../api/api-v3/hal-resources/user-resource.service';
import {CollectionResource} from '../../api/api-v3/hal-resources/collection-resource.service';
import {
QueryFilterInstanceResource
} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {RootDmService} from '../../api/api-v3/hal-resource-dms/root-dm.service';
import {RootResource} from '../../api/api-v3/hal-resources/root-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
import {RootResource} from 'core-app/modules/hal/resources/root-resource';
import {PathHelperService} from '../../common/path-helper/path-helper.service';
import {$injectFields} from '../../angular/angular-injector-bridge.functions';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {RootDmService} from 'core-app/modules/dm-services/root-dm.service';
import {Injector} from '@angular/core';
export class ToggledMultiselectController {
// Injected
@ -51,6 +49,7 @@ export class ToggledMultiselectController {
public text:{ [key: string]: string; };
constructor(public $scope:ng.IScope,
public injector:Injector,
private I18n:op.I18n,
private $q:ng.IQService,
private RootDm:RootDmService) {
@ -135,7 +134,7 @@ export class ToggledMultiselectController {
return;
}
let me:HalResource = new HalResource({
let me:HalResource = new HalResource(this.injector, {
_links: {
self: {
href: this.PathHelper.apiV3UserMePath(),

@ -27,10 +27,10 @@
// ++
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service';
import {QueryFilterInstanceSchemaResource} from '../../api/api-v3/hal-resources/query-filter-instance-schema-resource.service';
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {QueryOperatorResource} from '../../api/api-v3/hal-resources/query-operator-resource.service';
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource';
import {QueryFilterInstanceSchemaResource} from 'core-app/modules/hal/resources/query-filter-instance-schema-resource';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {QueryOperatorResource} from 'core-app/modules/hal/resources/query-operator-resource';
import {WorkPackageTableFiltersService} from '../../wp-fast-table/state/wp-table-filters.service';
function queryFilterDirective($animate:any,

@ -27,11 +27,11 @@
//++
import {filtersModule} from '../../../angular-modules';
import {QueryFilterInstanceSchemaResource} from '../../api/api-v3/hal-resources/query-filter-instance-schema-resource.service'
import {QueryFilterInstanceResource} from '../../api/api-v3/hal-resources/query-filter-instance-resource.service'
import {QueryFilterResource} from '../../api/api-v3/hal-resources/query-filter-resource.service'
import {QueryResource} from '../../api/api-v3/hal-resources/query-resource.service'
import {FormResource} from '../../api/api-v3/hal-resources/form-resource.service'
import {QueryFilterInstanceSchemaResource} from 'core-app/modules/hal/resources/query-filter-instance-schema-resource'
import {QueryFilterInstanceResource} from 'core-app/modules/hal/resources/query-filter-instance-resource'
import {QueryFilterResource} from 'core-app/modules/hal/resources/query-filter-resource'
import {QueryResource} from 'core-app/modules/hal/resources/query-resource'
import {FormResource} from 'core-app/modules/hal/resources/form-resource'
import {WorkPackageTableFiltersService} from '../../wp-fast-table/state/wp-table-filters.service';
import WorkPackageFiltersService from "../../filters/wp-filters/wp-filters.service";

@ -62,7 +62,7 @@ export class ConfirmDialogService {
/**
* Confirm an action with an ng dialog with the given options
*/
public confirm(options:ConfirmDialogOptions):ng.IPromise<void> {
public confirm(options:ConfirmDialogOptions):Promise<void> {
const deferred = this.$q.defer<void>();
const scope:any = this.$rootScope.$new();
let dialog:IDialogOpenResult;

@ -27,19 +27,19 @@
//++
import {wpControllersModule} from '../../../angular-modules';
import {WorkPackageCollectionResource} from '../../api/api-v3/hal-resources/wp-collection-resource.service';
import {HalLink} from '../../api/api-v3/hal-link/hal-link.service';
import {WorkPackageTableColumnsService} from '../../wp-fast-table/state/wp-table-columns.service';
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource';
import {HalLink} from 'core-app/modules/hal/hal-link/hal-link';
interface ExportLink extends HalLink {
identifier:string;
}
class ExportModalController {
public name: string;
public closeMe: Function;
public exportOptions: any;
public name:string;
public closeMe:Function;
public exportOptions:any;
constructor(exportModal:any,
private UrlParamsHelper:any,
@ -71,9 +71,11 @@ class ExportModalController {
private addColumnsToHref(href:string) {
let columns = this.wpTableColumns.getColumns();
let columnIds = columns.map(function(column) { return column.id; });
let columnIds = columns.map(function (column) {
return column.id;
});
return href + "&" + this.UrlParamsHelper.buildQueryString({'columns[]': columnIds});
return href + "&" + this.UrlParamsHelper.buildQueryString({ 'columns[]': columnIds });
}
}

@ -30,8 +30,7 @@ import {wpControllersModule} from '../../../angular-modules';
import {WorkPackagesListService} from '../../wp-list/wp-list.service';
import {States} from '../../states.service';
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service';
import {QueryResource} from '../../api/api-v3/hal-resources/query-resource.service';
import {QueryDmService} from '../../api/api-v3/hal-resource-dms/query-dm.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
function SaveModalController(this:any,
$scope:any,
@ -73,7 +72,7 @@ function SaveModalController(this:any,
return $q.when(true);
})
.catch((error:any) => wpNotificationsService.handleErrorResponse(error))
.finally(() => this.isBusy = false);
.then(() => this.isBusy = false); // Same as .finally()
};
}

@ -29,7 +29,7 @@
import {wpControllersModule} from '../../../angular-modules';
import {States} from '../../states.service';
import {WorkPackagesListService} from '../../wp-list/wp-list.service';
import {QueryResource} from '../../api/api-v3/hal-resources/query-resource.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
export class ShareModalController {
public query:QueryResource;

@ -32,7 +32,7 @@ import {
QUERY_SORT_BY_ASC,
QUERY_SORT_BY_DESC,
QuerySortByResource
} from '../../api/api-v3/hal-resources/query-sort-by-resource.service';
} from 'core-app/modules/hal/resources/query-sort-by-resource';
import {QueryColumn} from '../../wp-query/query-column';
class SortModalObject {

@ -28,13 +28,13 @@
import {wpControllersModule} from '../../../angular-modules';
import {States} from '../../states.service';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
export class WorkPackageDestroyModalController {
public text:any;
public workPackages:WorkPackageResourceInterface[];
public workPackages:WorkPackageResource[];
public workPackageLabel:string;
constructor(private $scope:any,
@ -57,11 +57,11 @@ export class WorkPackageDestroyModalController {
title: I18n.t('js.modals.destroy_work_package.title', { label: this.workPackageLabel }),
text: I18n.t('js.modals.destroy_work_package.text', { label: this.workPackageLabel, count: this.workPackages.length }),
childCount: (wp:WorkPackageResourceInterface) => {
childCount: (wp:WorkPackageResource) => {
const count = this.children(wp).length;
return this.I18n.t('js.units.child_work_packages', {count: count});
},
hasChildren: (wp:WorkPackageResourceInterface) =>
hasChildren: (wp:WorkPackageResource) =>
I18n.t('js.modals.destroy_work_package.has_children', {childUnits: this.text.childCount(wp) }),
deletesChildren: I18n.t('js.modals.destroy_work_package.deletes_children')
};
@ -89,10 +89,10 @@ export class WorkPackageDestroyModalController {
});
}
public childLabel (workPackage:WorkPackageResourceInterface) {
public childLabel (workPackage:WorkPackageResource) {
}
public children(workPackage:WorkPackageResourceInterface) {
public children(workPackage:WorkPackageResource) {
if (workPackage.hasOwnProperty('children')) {
return workPackage.children;
} else {

@ -38,8 +38,6 @@ import {
sortingModalToken,
timelinesModalToken
} from 'core-app/angular4-transition-utils';
import {QueryFormResource} from 'core-components/api/api-v3/hal-resources/query-form-resource.service';
import {QueryResource} from 'core-components/api/api-v3/hal-resources/query-resource.service';
import {AuthorisationService} from 'core-components/common/model-auth/model-auth.service';
import {OpContextMenuTrigger} from 'core-components/op-context-menu/handlers/op-context-menu-trigger.directive';
import {OPContextMenuService} from 'core-components/op-context-menu/op-context-menu.service';
@ -53,6 +51,8 @@ import {WorkPackageTableTimelineService} from 'core-components/wp-fast-table/sta
import {WorkPackagesListService} from 'core-components/wp-list/wp-list.service';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {QueryFormResource} from 'core-app/modules/hal/resources/query-form-resource';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
@Directive({
selector: '[opSettingsContextMenu]'

@ -31,10 +31,10 @@ import {StateService} from '@uirouter/core';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {Directive, ElementRef, Inject, Input} from "@angular/core";
import {$stateToken} from "core-app/angular4-transition-utils";
import {TypeResource} from "core-components/api/api-v3/hal-resources/type-resource.service";
import {CollectionResource} from "core-components/api/api-v3/hal-resources/collection-resource.service";
import {LinkHandling} from "core-components/common/link-handling/link-handling";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
@Directive({
selector: '[opTypesCreateDropdown]'

@ -31,7 +31,7 @@ import {Directive, ElementRef} from "@angular/core";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service";
import {States} from "core-components/states.service";
import {FormResourceInterface} from "core-components/api/api-v3/hal-resources/form-resource.service";
import {FormResource} from 'core-app/modules/hal/resources/form-resource';
@Directive({
selector: '[wpCreateSettingsMenu]'
@ -52,7 +52,7 @@ export class WorkPackageCreateSettingsMenuDirective extends OpContextMenuTrigger
if (wp) {
const changeset = this.wpEditing.changesetFor(wp);
changeset.getForm().then(
(loadedForm:FormResourceInterface) => {
(loadedForm:FormResource) => {
this.buildItems(loadedForm);
this.opContextMenu.show(this, evt);
}
@ -73,7 +73,7 @@ export class WorkPackageCreateSettingsMenuDirective extends OpContextMenuTrigger
};
}
private buildItems(form:FormResourceInterface) {
private buildItems(form:FormResource) {
this.items = [];
const configureFormLink = form.configureForm;
const queryCustomFields = form.customFields;

@ -30,19 +30,19 @@ import {StateService} from '@uirouter/core';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {Directive, ElementRef, Inject, Input} from "@angular/core";
import {$stateToken} from "core-app/angular4-transition-utils";
import {CollectionResource} from "core-components/api/api-v3/hal-resources/collection-resource.service";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service";
import {WorkPackageTableRefreshService} from "core-components/wp-table/wp-table-refresh-request.service";
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {WorkPackageResourceInterface} from "core-components/api/api-v3/hal-resources/work-package-resource.service";
import {HalResource} from "core-components/api/api-v3/hal-resources/hal-resource.service";
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
@Directive({
selector: '[wpStatusDropdown]'
})
export class WorkPackageStatusDropdownDirective extends OpContextMenuTrigger {
@Input('wpStatusDropdown-workPackage') public workPackage:WorkPackageResourceInterface;
@Input('wpStatusDropdown-workPackage') public workPackage:WorkPackageResource;
constructor(readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,

@ -7,7 +7,7 @@ import {
} from "core-app/angular4-transition-utils";
import {LinkHandling} from "core-components/common/link-handling/link-handling";
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {WorkPackageResourceInterface} from "core-components/api/api-v3/hal-resources/work-package-resource.service";
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
import {PERMITTED_CONTEXT_MENU_ACTIONS} from "core-components/op-context-menu/wp-context-menu/wp-static-context-menu-actions";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {WorkPackageAuthorization} from "core-components/work-packages/work-package-authorization.service";
@ -18,7 +18,7 @@ import {StateService} from "@uirouter/core";
selector: '[wpSingleContextMenu]'
})
export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger {
@Input('wpSingleContextMenu-workPackage') public workPackage:WorkPackageResourceInterface;
@Input('wpSingleContextMenu-workPackage') public workPackage:WorkPackageResource;
constructor(@Inject(HookServiceToken) readonly HookService:any,
@Inject(wpDestroyModalToken) readonly wpDestroyModal:any,

@ -26,12 +26,12 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {UserResource} from '../../api/api-v3/hal-resources/user-resource.service';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageViewController} from '../wp-view-base/wp-view-base.controller';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
import {TypeResource} from 'core-components/api/api-v3/hal-resources/type-resource.service';
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';
import {Component, Inject, Injector} from '@angular/core';
import {$stateToken} from 'core-app/angular4-transition-utils';
import {WorkPackageTableSelection} from 'core-components/wp-fast-table/state/wp-table-selection.service';
@ -97,7 +97,7 @@ export class WorkPackagesFullViewComponent extends WorkPackageViewController {
this.$state.go('work-packages.list', this.$state.params);
}
private setWorkPackageScopeProperties(wp:WorkPackageResourceInterface) {
private setWorkPackageScopeProperties(wp:WorkPackageResource) {
this.isWatched = wp.hasOwnProperty('unwatch');
this.displayWatchButton = wp.hasOwnProperty('unwatch') || wp.hasOwnProperty('watch');

@ -35,7 +35,7 @@ import {TableState} from 'core-components/wp-table/table-state/table-state';
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {auditTime, distinctUntilChanged, filter, take, withLatestFrom} from 'rxjs/operators';
import {debugLog} from '../../../helpers/debug_output';
import {QueryResource} from '../../api/api-v3/hal-resources/query-resource.service';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {LoadingIndicatorService} from '../../common/loading-indicator/loading-indicator.service';
import {States} from '../../states.service';
import {WorkPackageQueryStateService} from '../../wp-fast-table/state/wp-table-base.service';

@ -32,12 +32,12 @@ import {PathHelperService} from 'core-components/common/path-helper/path-helper.
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {States} from '../../states.service';
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service';
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
import {KeepTabService} from '../../wp-single-view-tabs/keep-tab/keep-tab.service';
import {WorkPackageTableRefreshService} from '../../wp-table/wp-table-refresh-request.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
export class WorkPackageViewController implements OnDestroy {
@ -54,7 +54,7 @@ export class WorkPackageViewController implements OnDestroy {
public text:any = {};
// Work package resource to be loaded from the cache
public workPackage:WorkPackageResourceInterface;
public workPackage:WorkPackageResource;
public projectIdentifier:string;
protected focusAnchorLabel:string;
@ -77,7 +77,7 @@ export class WorkPackageViewController implements OnDestroy {
.pipe(
takeUntil(componentDestroyed(this))
)
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
this.workPackage = wp;
this.init();
});

@ -27,12 +27,9 @@
// ++
import {State} from "reactivestates";
import {States} from "../states.service";
import {SchemaResource} from "../api/api-v3/hal-resources/schema-resource.service";
import {WorkPackageResourceInterface} from "core-components/api/api-v3/hal-resources/work-package-resource.service";
import {Injectable} from '@angular/core';
import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service';
import {opWorkPackagesModule} from 'core-app/angular-modules';
import {downgradeInjectable} from '@angular/upgrade/static';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
@Injectable()
export class SchemaCacheService {
@ -46,7 +43,7 @@ export class SchemaCacheService {
* @param href The schema's href.
* @return A promise with the loaded schema.
*/
ensureLoaded(workPackage:WorkPackageResourceInterface):PromiseLike<any> {
ensureLoaded(workPackage:WorkPackageResource):PromiseLike<any> {
const state = this.state(workPackage);
if (state.hasValue()) {
@ -59,8 +56,8 @@ export class SchemaCacheService {
/**
* Get the associated schema state of the work package
* without initializing a new resource.
*/
state(workPackage:WorkPackageResourceInterface) {
*/
state(workPackage:WorkPackageResource) {
const schema = workPackage.$links.schema;
return this.states.schemas.get(schema.href!);
}
@ -68,7 +65,7 @@ export class SchemaCacheService {
/**
* Load the associated schema for the given work package, if needed.
*/
load(workPackage:WorkPackageResourceInterface, forceUpdate = false):State<SchemaResource> {
load(workPackage:WorkPackageResource, forceUpdate = false):State<SchemaResource> {
const state = this.state(workPackage);
if (forceUpdate) {
@ -83,5 +80,3 @@ export class SchemaCacheService {
return state;
}
}
opWorkPackagesModule.service('schemaCacheService', downgradeInjectable(SchemaCacheService));

@ -3,22 +3,22 @@ import {TableState} from 'core-components/wp-table/table-state/table-state';
import {combine, derive, input, multiInput, StatesGroup} from 'reactivestates';
import {mapTo} from 'rxjs/operators';
import {opServicesModule} from '../angular-modules';
import {QueryFormResource} from './api/api-v3/hal-resources/query-form-resource.service';
import {QueryGroupByResource} from './api/api-v3/hal-resources/query-group-by-resource.service';
import {QueryResource} from './api/api-v3/hal-resources/query-resource.service';
import {QuerySortByResource} from './api/api-v3/hal-resources/query-sort-by-resource.service';
import {SchemaResource} from './api/api-v3/hal-resources/schema-resource.service';
import {TypeResource} from './api/api-v3/hal-resources/type-resource.service';
import {WorkPackageResourceInterface} from './api/api-v3/hal-resources/work-package-resource.service';
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 {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {SwitchState} from './states/switch-state';
import {QueryColumn} from './wp-query/query-column';
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';
export class States extends StatesGroup {
name = 'MainStore';
/* /api/v3/work_packages */
workPackages = multiInput<WorkPackageResourceInterface>();
workPackages = multiInput<WorkPackageResource>();
/* /api/v3/schemas */
schemas = multiInput<SchemaResource>();

@ -27,8 +27,8 @@
//++
import {Injectable} from '@angular/core';
import {ConfigurationDmService} from "core-components/api/api-v3/hal-resource-dms/configuration-dm.service";
import {opServicesModule} from "../../angular-modules";
import {ConfigurationDmService} from 'core-app/modules/dm-services/configuration-dm.service';
export const DEFAULT_PAGINATION_OPTIONS = {
maxVisiblePageOptions: 6,
@ -44,7 +44,7 @@ export interface IPaginationOptions {
@Injectable()
export class PaginationService {
private paginationOptions: IPaginationOptions;
private paginationOptions:IPaginationOptions;
constructor(private ConfigurationDm:ConfigurationDmService) {
const gonPaginationOptions = this.getInitialPageOptions();
@ -60,7 +60,7 @@ export class PaginationService {
public getInitialPageOptions():number[] {
try {
return (window as any).gon.settings.pagination.per_page_options;
} catch(e) {
} catch (e) {
console.log("Can't load initial page options from gon: " + e);
return [];
}

@ -27,7 +27,7 @@
//++
import {openprojectModule} from '../../../angular-modules';
import {UserResource} from '../../api/api-v3/hal-resources/user-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
interface UserLinkScope {
user:UserResource;

@ -28,7 +28,7 @@
import {Component, Directive, ElementRef, Injector, Input, Output} from '@angular/core';
import {UpgradeComponent} from '@angular/upgrade/static';
import {UserResource} from 'core-components/api/api-v3/hal-resources/user-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
@Directive({
selector: 'user-link'

@ -28,7 +28,7 @@
import {opWorkPackagesModule} from '../../angular-modules';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {StateService} from '@uirouter/core';
var $state:StateService;
@ -38,7 +38,7 @@ export class WorkPackageAuthorization {
public project:any;
constructor(public workPackage:WorkPackageResourceInterface) {
constructor(public workPackage:WorkPackageResource) {
this.project = workPackage.project;
}

@ -26,23 +26,21 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {SchemaCacheService} from 'core-components/schemas/schema-cache.service';
import {TestBed} from '@angular/core/testing';
require('../../angular4-test-setup');
import {
WorkPackageResource,
WorkPackageResourceInterface
} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageCacheService} from "./work-package-cache.service";
import {TestBed} from '@angular/core/testing';
import {take, takeUntil, takeWhile} from 'rxjs/operators';
import {SchemaCacheService} from 'core-components/schemas/schema-cache.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service';
import {ApiWorkPackagesService} from 'core-components/api/api-work-packages/api-work-packages.service';
import {States} from 'core-components/states.service';
import {WorkPackageNotificationService} from 'core-components/wp-edit/wp-notification.service';
import {$q} from '@uirouter/core';
import {States} from 'core-components/states.service';
import {take, takeWhile} from 'rxjs/operators';
import {Injector} from '@angular/core';
describe('WorkPackageCacheService', () => {
let injector:Injector;
let wpCacheService:WorkPackageCacheService;
let schemaCacheService:SchemaCacheService;
let dummyWorkPackages:WorkPackageResource[] = [];
@ -59,12 +57,15 @@ describe('WorkPackageCacheService', () => {
]
});
injector = TestBed.get(Injector);
wpCacheService = TestBed.get(WorkPackageCacheService);
schemaCacheService = TestBed.get(SchemaCacheService);
sinon.stub(schemaCacheService, 'ensureLoaded').returns(Promise.resolve(true));
const workPackage1 = new WorkPackageResource({
const workPackage1 = new WorkPackageResource(
injector,
{
id: '1',
_links: {
self: ""
@ -79,12 +80,12 @@ describe('WorkPackageCacheService', () => {
.pipe(
take(1)
)
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
expect(wp.id).to.eq('1');
done();
});
wpCacheService.updateWorkPackageList(dummyWorkPackages as WorkPackageResourceInterface[]);
wpCacheService.updateWorkPackageList(dummyWorkPackages as WorkPackageResource[]);
});
it('should return/stream a work package every time it gets updated', (done:any) => {
@ -94,7 +95,7 @@ describe('WorkPackageCacheService', () => {
.pipe(
takeWhile((wp) => count < 2)
)
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
expect(wp.id).to.eq('1');
count += 1;
@ -103,8 +104,8 @@ describe('WorkPackageCacheService', () => {
}
});
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResourceInterface[]);
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResourceInterface[]);
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResourceInterface[]);
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResource[]);
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResource[]);
wpCacheService.updateWorkPackageList([dummyWorkPackages[0]] as WorkPackageResource[]);
});
});

@ -28,23 +28,23 @@
import {State} from 'reactivestates';
import {opWorkPackagesModule} from '../../angular-modules';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {ApiWorkPackagesService} from '../api/api-work-packages/api-work-packages.service';
import {States} from '../states.service';
import {WorkPackageNotificationService} from './../wp-edit/wp-notification.service';
import {WorkPackageCollectionResourceInterface} from '../api/api-v3/hal-resources/wp-collection-resource.service';
import {SchemaResource} from '../api/api-v3/hal-resources/schema-resource.service';
import {StateCacheService} from '../states/state-cache.service';
import {downgradeInjectable} from '@angular/upgrade/static';
import {Injectable} from '@angular/core';
import {SchemaCacheService} from './../schemas/schema-cache.service';
import {WorkPackageCollectionResource} from 'core-app/modules/hal/resources/wp-collection-resource';
import {SchemaResource} from 'core-app/modules/hal/resources/schema-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {Injectable} from '@angular/core';
function getWorkPackageId(id:number | string):string {
return (id || '__new_work_package__').toString();
}
@Injectable()
export class WorkPackageCacheService extends StateCacheService<WorkPackageResourceInterface> {
export class WorkPackageCacheService extends StateCacheService<WorkPackageResource> {
/*@ngInject*/
constructor(private states:States,
@ -54,15 +54,15 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
super();
}
public updateValue(id:string, val:WorkPackageResourceInterface) {
public updateValue(id:string, val:WorkPackageResource) {
this.updateWorkPackageList([val]);
}
updateWorkPackage(wp:WorkPackageResourceInterface) {
updateWorkPackage(wp:WorkPackageResource) {
this.updateWorkPackageList([wp]);
}
updateWorkPackageList(list:WorkPackageResourceInterface[]) {
updateWorkPackageList(list:WorkPackageResource[]) {
for (var i of list) {
const wp = i;
const workPackageId = getWorkPackageId(wp.id);
@ -81,12 +81,12 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
}
}
async saveWorkPackage(workPackage:WorkPackageResourceInterface):Promise<WorkPackageResourceInterface | null> {
async saveWorkPackage(workPackage:WorkPackageResource):Promise<WorkPackageResource | null> {
if (!(workPackage.dirty || workPackage.isNew)) {
return Promise.reject<any>(null);
}
return new Promise<WorkPackageResourceInterface | null>((resolve, reject) => {
return new Promise<WorkPackageResource | null>((resolve, reject) => {
return workPackage.save()
.then(() => {
this.wpNotificationsService.showSave(workPackage);
@ -104,7 +104,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
*
* @deprecated
*/
loadWorkPackage(workPackageId:string, forceUpdate = false):State<WorkPackageResourceInterface> {
loadWorkPackage(workPackageId:string, forceUpdate = false):State<WorkPackageResource> {
const state = this.state(workPackageId);
// Several services involved in the creation of work packages
@ -122,7 +122,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
return new Promise<undefined>((resolve, reject) => {
this.apiWorkPackages
.loadWorkPackagesCollectionsFor(_.uniq(ids))
.then((pagedResults:WorkPackageCollectionResourceInterface[]) => {
.then((pagedResults:WorkPackageCollectionResource[]) => {
_.each(pagedResults, (results) => {
if (results.schemas) {
_.each(results.schemas.elements, (schema:SchemaResource) => {
@ -141,9 +141,9 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
}
protected async load(id:string) {
return new Promise<WorkPackageResourceInterface>((resolve, reject) => {
return new Promise<WorkPackageResource>((resolve, reject) => {
this.apiWorkPackages.loadWorkPackageById(id, true)
.then((workPackage:WorkPackageResourceInterface) => {
.then((workPackage:WorkPackageResource) => {
this.schemaCacheService.ensureLoaded(workPackage).then(() => {
this.multiState.get(id).putValue(workPackage);
resolve(workPackage);

@ -26,16 +26,16 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCommentField} from './wp-comment-field.module';
import {ErrorResource} from '../../api/api-v3/hal-resources/error-resource.service';
import {ErrorResource} from 'core-app/modules/hal/resources/error-resource';
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service';
import {WorkPackageCacheService} from '../work-package-cache.service';
import {LoadingIndicatorService} from '../../common/loading-indicator/loading-indicator.service';
import {scopedObservable} from 'core-app/helpers/angular-rx-utils';
export class CommentFieldDirectiveController {
public workPackage:WorkPackageResourceInterface;
public workPackage:WorkPackageResource;
public field:WorkPackageCommentField;
protected text:Object;

@ -29,14 +29,14 @@
import {UpgradeComponent} from '@angular/upgrade/static';
import {Directive, ElementRef, Injector, Input} from '@angular/core';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {CollectionResource} from 'core-components/api/api-v3/hal-resources/collection-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {CollectionResource} from 'core-app/modules/hal/resources/collection-resource';
@Directive({
selector: 'work-package-comment-upgraded'
})
export class WorkPackageCommentDirectiveUpgraded extends UpgradeComponent {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
@Input('activities') public activities:CollectionResource;
constructor(elementRef:ElementRef, injector:Injector) {

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WikiTextareaEditField} from '../../wp-edit/field-types/wp-edit-wiki-textarea-field.module';
import {WorkPackageChangeset} from '../../wp-edit-form/work-package-changeset';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
@ -38,7 +38,7 @@ export class WorkPackageCommentField extends WikiTextareaEditField {
public ConfigurationService:ConfigurationService = this.$injector.get(ConfigurationService);
constructor(public workPackage:WorkPackageResourceInterface, protected I18n:op.I18n) {
constructor(public workPackage:WorkPackageResource, protected I18n:op.I18n) {
super(
new WorkPackageChangeset(WorkPackageCommentField.$injector, workPackage),
'comment',

@ -1,5 +1,5 @@
import IAugmentedJQuery = angular.IAugmentedJQuery;
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
export class DropModel {
public files:File[];
@ -16,7 +16,7 @@ export class DropModel {
constructor(protected $location:ng.ILocationService,
protected dataTransfer:any,
protected workPackage:WorkPackageResourceInterface) {
protected workPackage:WorkPackageResource) {
this.files = <File[]>dataTransfer.files;
this.filesCount = this.files.length;
this.isUpload = this._isUpload(dataTransfer);

@ -1,20 +1,17 @@
import {InsertMode} from '../wp-attachments-formattable.enums';
import {IApplyAttachmentMarkup} from '../wp-attachments-formattable.interfaces';
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {MarkupModel} from './markup-model';
import {WorkPackageCacheService} from '../../work-package-cache.service';
import {$injectFields} from '../../../angular/angular-injector-bridge.functions';
import {WorkPackageChangeset} from '../../../wp-edit-form/work-package-changeset';
import {Injector} from '@angular/core';
export class WorkPackageFieldModel implements IApplyAttachmentMarkup {
public contentToInsert:string;
constructor(protected workPackage:WorkPackageResourceInterface,
constructor(protected workPackage:WorkPackageResource,
protected attribute:string,
protected markupModel:MarkupModel) {
const formattable = workPackage[attribute];
this.contentToInsert = _.get(formattable, 'raw') as string || '';
}

@ -1,5 +1,5 @@
import {InsertMode, ViewMode} from './wp-attachments-formattable.enums';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {KeepTabService} from '../../wp-single-view-tabs/keep-tab/keep-tab.service';
import {openprojectModule} from '../../../angular-modules';
import {WorkPackageCacheService} from '../work-package-cache.service';
@ -47,7 +47,7 @@ export class WpAttachmentsFormattableController {
const [, editor] = this.getEditor();
const originalEvent = (evt.originalEvent as DragEvent);
const workPackage:WorkPackageResourceInterface = this.$scope.workPackage;
const workPackage:WorkPackageResource = this.$scope.workPackage;
const dropData:DropModel = new DropModel(this.$location,
originalEvent.dataTransfer,
workPackage);
@ -109,7 +109,7 @@ export class WpAttachmentsFormattableController {
}
protected uploadAndInsert(files:UploadFile[], model:EditorModel | WorkPackageFieldModel) {
const wp = this.$scope.workPackage as WorkPackageResourceInterface;
const wp = this.$scope.workPackage as WorkPackageResource;
if (wp.isNew) {
return this.insertDelayedAttachments(files, model, wp);
}
@ -160,7 +160,7 @@ export class WpAttachmentsFormattableController {
}
}
protected insertDelayedAttachments(files:UploadFile[], description:any, workPackage:WorkPackageResourceInterface):void {
protected insertDelayedAttachments(files:UploadFile[], description:any, workPackage:WorkPackageResource):void {
for (var i = 0; i < files.length; i++) {
var currentFile = new SingleAttachmentModel(files[i]);
var insertMode = currentFile.isAnImage ? InsertMode.INLINE : InsertMode.ATTACHMENT;

@ -29,14 +29,14 @@
import {wpControllersModule} from '../../../angular-modules';
import {Component, Input} from '@angular/core';
import {downgradeComponent} from '@angular/upgrade/static';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
@Component({
template: require('!!raw-loader!./wp-breadcrumb.html'),
selector: 'wp-breadcrumb',
})
export class WorkPackageBreadcrumbComponent {
@Input('workPackage') workPackage:WorkPackageResourceInterface;
@Input('workPackage') workPackage:WorkPackageResource;
}
wpControllersModule.directive(

@ -28,12 +28,12 @@
import {opWorkPackagesModule} from './../../../angular-modules';
import {WorkPackageChangeset} from './../../wp-edit-form/work-package-changeset';
import {WorkPackageResourceInterface} from './../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
export class WorkPackageEditorFieldController {
public wpEditFieldGroup:WorkPackageEditFieldGroupComponent;
public workPackage:WorkPackageResourceInterface;
public workPackage:WorkPackageResource;
public attribute:string;
public wrapperClasses:string;

@ -33,9 +33,9 @@ import {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edi
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {debugLog} from '../../../helpers/debug_output';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {CurrentProjectService} from '../../projects/current-project.service';
import {States} from '../../states.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {DisplayField} from '../../wp-display/wp-display-field/wp-display-field.module';
import {WorkPackageDisplayFieldService} from '../../wp-display/wp-display-field/wp-display-field.service';
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
@ -60,7 +60,7 @@ interface GroupDescriptor {
selector: 'wp-single-view',
})
export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
// Grouped fields returned from API
public groupedFields:GroupDescriptor[] = [];
@ -117,7 +117,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
.pipe(
takeUntil(componentDestroyed(this))
)
.subscribe((resource:WorkPackageResourceInterface) => {
.subscribe((resource:WorkPackageResource) => {
// Prepare the fields that are required always
const isNew = this.workPackage.isNew;
@ -201,7 +201,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
* Maps the grouped fields into their display fields.
* May return multiple fields (for the date virtual field).
*/
private getFields(resource:WorkPackageResourceInterface, fieldNames:string[]):FieldDescriptor[] {
private getFields(resource:WorkPackageResource, fieldNames:string[]):FieldDescriptor[] {
const descriptors:FieldDescriptor[] = [];
fieldNames.forEach((fieldName:string) => {
@ -233,7 +233,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
* 'date' field vs. all other types which should display a
* combined 'start' and 'due' date field.
*/
private getDateField(resource:WorkPackageResourceInterface):FieldDescriptor {
private getDateField(resource:WorkPackageResource):FieldDescriptor {
let object:any = {
name: 'date',
label: this.I18n.t('js.work_packages.properties.date'),
@ -250,7 +250,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
return object;
}
private displayField(resource:WorkPackageResourceInterface, name:string):DisplayField {
private displayField(resource:WorkPackageResource, name:string):DisplayField {
return this.wpDisplayField.getField(
resource,
name,

@ -27,12 +27,10 @@
// ++
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {downgradeComponent} from '@angular/upgrade/static';
import {UIRouterGlobals} from '@uirouter/core';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {opWorkPackagesModule} from '../../../angular-modules';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCacheService} from '../work-package-cache.service';
@Component({
@ -40,7 +38,7 @@ import {WorkPackageCacheService} from '../work-package-cache.service';
selector: 'wp-subject',
})
export class WorkPackageSubjectComponent implements OnInit, OnDestroy {
@Input('workPackage') workPackage:WorkPackageResourceInterface;
@Input('workPackage') workPackage:WorkPackageResource;
constructor(protected uiRouterGlobals:UIRouterGlobals,
protected wpCacheService:WorkPackageCacheService) {
@ -57,15 +55,9 @@ export class WorkPackageSubjectComponent implements OnInit, OnDestroy {
.pipe(
takeUntil(componentDestroyed(this))
)
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
this.workPackage = wp;
});
}
}
}
opWorkPackagesModule.directive(
'wpSubject',
downgradeComponent({component: WorkPackageSubjectComponent})
);

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {Component, Input} from '@angular/core';
@Component({
@ -34,5 +34,5 @@ import {Component, Input} from '@angular/core';
selector: 'wp-type-status',
})
export class WorkPackageTypeStatusComponent {
@Input('workPackage') workPackage:WorkPackageResourceInterface;
@Input('workPackage') workPackage:WorkPackageResource;
}

@ -26,21 +26,19 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCacheService} from '../work-package-cache.service';
import {Component, Inject, Input, OnDestroy, OnInit} from '@angular/core';
import {downgradeComponent} from '@angular/upgrade/static';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {takeUntil} from 'rxjs/operators';
import {wpDirectivesModule} from '../../../angular-modules';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageCacheService} from '../work-package-cache.service';
@Component({
template: require('!!raw-loader!./wp-watcher-button.html'),
selector: 'wp-watcher-button',
})
export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
@Input('workPackage') public workPackage:WorkPackageResource;
@Input('showText') public showText:boolean = false;
@Input('disabled') public disabled:boolean = false;
@ -60,7 +58,7 @@ export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
.pipe(
takeUntil(componentDestroyed(this))
)
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
this.workPackage = wp;
this.setWatchStatus();
});
@ -108,7 +106,3 @@ export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
}
}
}
wpDirectivesModule.directive('wpWatcherButton',
downgradeComponent({component: WorkPackageWatcherButtonComponent})
);

@ -31,15 +31,15 @@ import {
SimpleChanges
} from '@angular/core';
import {UpgradeComponent} from '@angular/upgrade/static';
import {UserResource} from 'core-components/api/api-v3/hal-resources/user-resource.service';
import {HalResource} from '../api/api-v3/hal-resources/hal-resource.service';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
@Directive({
selector: 'activity-entry-upgraded'
})
export class ActivityEntryDirectiveUpgraded extends UpgradeComponent {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
@Input('activity') public activity:HalResource;
@Input('activityNo') public activityNo:number;
@Input('isInitial') public isInitial:boolean;

@ -28,9 +28,9 @@
/* globals URI */
import {opServicesModule} from './../../angular-modules';
import {WorkPackageResourceInterface} from 'app/components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageNotificationService} from './../wp-edit/wp-notification.service';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {input} from 'reactivestates';
export class ActivityService {
@ -47,7 +47,7 @@ export class ActivityService {
private $q:ng.IQService) {
}
public createComment(workPackage:WorkPackageResourceInterface, comment:string) {
public createComment(workPackage:WorkPackageResource, comment:string) {
return workPackage.addComment(
{ comment: comment},
{ 'Content-Type': 'application/json; charset=UTF-8' }
@ -76,7 +76,7 @@ export class ActivityService {
}).catch((error:any) => this.errorAndReject(error));
}
private errorAndReject(error:HalResource, workPackage?:WorkPackageResourceInterface) {
private errorAndReject(error:HalResource, workPackage?:WorkPackageResource) {
this.wpNotificationsService.handleErrorResponse(error, workPackage);
// returning a reject will enable to correctly work with subsequent then/catch handlers.

@ -1,131 +0,0 @@
//-- 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 {UserResource} from '../../api/api-v3/hal-resources/user-resource.service';
describe('revisionActivity Directive', function () {
var compile:any, element:any, rootScope:any, scope:any, I18n:any, $q:any;
beforeEach(angular.mock.module('openproject.workPackages.activities'));
beforeEach(function () {
angular.mock.module(
'openproject.api',
'openproject.workPackages',
'openproject.models',
'openproject.services',
'openproject.config',
'openproject.templates'
);
});
beforeEach(inject(function ($rootScope:any, $compile:any, _I18n_:any, _$q_:any) {
var html:string;
html = '<revision-activity work-package="workPackage" activity="activity" activity-no="activityNo" is-initial="isInitial"></revision-activity>';
rootScope = $rootScope;
$q = _$q_;
scope = $rootScope.$new();
I18n = _I18n_;
sinon.stub(I18n, 't').returns('');
compile = function () {
element = angular.element(html);
$compile(element)(scope);
scope.$digest();
};
}));
afterEach(function () {
I18n.t.restore();
});
describe('with a valid revision', function () {
beforeEach(function () {
scope.workPackage = {
revisions: true
};
scope.activity = {
showRevision: {
$link: { href: '/project/foo/repository/revision/1234' },
},
id: 1,
identifier: '11f4b07dff4f4ce9548a52b7d002daca7cd63ec6',
formattedIdentifier: '11f4b07',
authorName: 'some developer',
message: {
format: 'plain',
raw: 'This revision provides new features\n\nAn elaborate description',
html: '<p>This revision provides new features<br><br>An elaborate description</p>'
},
createdAt: '2015-07-21T13:36:59Z'
};
compile();
});
it('should not render an image', function () {
expect(element.find('.avatar')).to.have.length(0);
});
it('should have the author name, but no link', function () {
expect(element.find('.user').html()).to.equal('some developer');
expect(element.find('.user > a')).to.have.length(0);
});
describe('with linked author', function () {
beforeEach(function () {
scope.activity.author = {
$load: function () {
return $q.when(new UserResource({
id: 1,
name: 'Some Dude',
avatar: 'avatar.png',
status: 'active'
}, true));
}
};
compile();
});
it('should render a user profile', function () {
expect(element.find('.avatar').attr('alt')).to.equal('Avatar');
expect(element.find('span.user > a').text()).to.equal('Some Dude');
});
});
describe('message', function () {
it('should render commit message', function () {
var message = element.find('.user-comment > span.message').html();
expect(message).to.eq(scope.activity.message.html);
});
});
});
});

@ -26,19 +26,18 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {UserResource} from '../../api/api-v3/hal-resources/user-resource.service';
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service';
import {TextileService} from './../../common/textile/textile-service';
import {ActivityService} from './../activity-service';
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {ActivityEntryInfo} from 'core-components/wp-single-view-tabs/activity-panel/activity-entry-info';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {WorkPackageCommentField} from 'core-components/work-packages/work-package-comment/wp-comment-field.module';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
export class UserActivityController {
public workPackage:WorkPackageResourceInterface;
public workPackage:WorkPackageResource;
public activity:HalResource;
public activityNo:string;
public activityLabel:string;

@ -26,18 +26,18 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service';
import {Component, Inject, Input} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
@Component({
template: require('!!raw-loader!./wp-attachment-list-item.html'),
selector: 'wp-attachment-list-item',
})
export class WorkPackageAttachmentListItemComponent {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
@Input('attachment') public attachment:HalResource;
public text = {

@ -26,16 +26,16 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {Component, Input, OnInit} from '@angular/core';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
@Component({
template: require('!!raw-loader!./wp-attachment-list.html'),
selector: 'wp-attachment-list',
})
export class WorkPackageAttachmentListComponent implements OnInit {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
ngOnInit() {
if (this.workPackage.attachments) {

@ -32,11 +32,11 @@ import {
OnInit, SimpleChanges
} from '@angular/core';
import {UpgradeComponent} from '@angular/upgrade/static';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
@Directive({selector: 'ng1-wp-attachments-upload-wrapper'})
export class Ng1WorkPackageAttachmentsUploadWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
@Input('workPackage') workPackage:WorkPackageResourceInterface;
@Input('workPackage') workPackage:WorkPackageResource;
constructor(@Inject(ElementRef) elementRef:ElementRef, @Inject(Injector) injector:Injector) {
// We must pass the name of the directive as used by AngularJS to the super

@ -27,12 +27,12 @@
//++
import {wpDirectivesModule} from '../../../angular-modules';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {UploadFile} from '../../api/op-file-upload/op-file-upload.service';
import IDirective = angular.IDirective;
export class WorkPackageUploadDirectiveController {
public workPackage: WorkPackageResourceInterface;
public workPackage: WorkPackageResource;
public text: any;
public maxFileSize: number;

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageEditingService} from 'core-components/wp-edit-form/work-package-editing-service';
import {Component, Inject, Input} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
@ -36,7 +36,7 @@ import {I18nToken} from 'core-app/angular4-transition-utils';
selector: 'wp-status-button',
})
export class WorkPackageStatusButtonComponent {
@Input('workPackage') public workPackage:WorkPackageResourceInterface;
@Input('workPackage') public workPackage:WorkPackageResource;
@Input('allowed') public allowed:boolean;
public text = {

@ -27,12 +27,12 @@
// ++
import {wpButtonsModule} from '../../../angular-modules';
import {TimelineZoomLevel} from '../../api/api-v3/hal-resources/query-resource.service';
import {WorkPackageTableTimelineService} from '../../wp-fast-table/state/wp-table-timeline.service';
import {AbstractWorkPackageButtonComponent, ButtonControllerText} from '../wp-buttons.module';
import {Component, Inject} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {downgradeComponent} from '@angular/upgrade/static';
import {TimelineZoomLevel} from 'core-app/modules/hal/resources/query-resource';
interface TimelineButtonText extends ButtonControllerText {
zoomOut:string;

@ -27,9 +27,9 @@
// ++
import {take} from 'rxjs/operators';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset';
import {WorkPackageCreateController} from '../wp-new/wp-create.controller';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageChangeset} from 'core-components/wp-edit-form/work-package-changeset';
import {WorkPackageCreateController} from 'core-components/wp-new/wp-create.controller';
export class WorkPackageCopyController extends WorkPackageCreateController {
protected async newWorkPackageFromParams(stateParams:any) {
@ -40,12 +40,12 @@ export class WorkPackageCopyController extends WorkPackageCreateController {
take(1)
)
.subscribe(
async (wp:WorkPackageResourceInterface) => this.createCopyFrom(wp).then(resolve),
async (wp:WorkPackageResource) => this.createCopyFrom(wp).then(resolve),
reject);
});
}
private async createCopyFrom(wp:WorkPackageResourceInterface) {
private async createCopyFrom(wp:WorkPackageResource) {
const changeset = this.wpEditing.changesetFor(wp);
return changeset.getForm().then((form:any) => {
return this.wpCreate.copyWorkPackage(form, wp.project.identifier);

@ -30,7 +30,7 @@ import {opUiComponentsModule} from '../../angular-modules';
import {Component} from '@angular/core';
import {OnInit, Input} from '@angular/core';
import {downgradeComponent} from '@angular/upgrade/static';
import {WorkPackageResource} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
@Component({
selector: 'wp-custom-actions',

@ -29,13 +29,13 @@
import {opUiComponentsModule} from '../../../angular-modules';
import {Component, HostListener, Inject, Input} from '@angular/core';
import {CustomActionResourceInterface} from 'core-components/api/api-v3/hal-resources/custom-action-resource.service';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {HalRequestService} from 'core-components/api/api-v3/hal-request/hal-request.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageCacheService} from 'core-components/work-packages/work-package-cache.service';
import {WorkPackageNotificationService} from 'core-components/wp-edit/wp-notification.service';
import {downgradeComponent} from '@angular/upgrade/static';
import {halRequestToken} from 'core-app/angular4-transition-utils';
import {HalRequestService} from 'core-app/modules/hal/services/hal-request.service';
import {CustomActionResource} from 'core-app/modules/hal/resources/custom-action-resource';
@Component({
selector: 'wp-custom-action',
@ -43,17 +43,18 @@ import {halRequestToken} from 'core-app/angular4-transition-utils';
})
export class WpCustomActionComponent {
@Input() workPackage:WorkPackageResourceInterface;
@Input() action:CustomActionResourceInterface;
@Input() workPackage:WorkPackageResource;
@Input() action:CustomActionResource;
constructor(@Inject(halRequestToken) private halRequest:HalRequestService,
private wpCacheService:WorkPackageCacheService,
private wpNotificationsService:WorkPackageNotificationService) { }
private fetchAction() {
this.halRequest.get(this.action.href!)
this.halRequest.get<CustomActionResource>(this.action.href!)
.toPromise()
.then((action) => {
this.action = <CustomActionResourceInterface>action;
this.action = <CustomActionResource>action;
});
}
@ -67,8 +68,9 @@ export class WpCustomActionComponent {
}
};
this.halRequest.post<WorkPackageResourceInterface>(this.action.href + '/execute', payload)
.then((savedWp:WorkPackageResourceInterface) => {
this.halRequest.post<WorkPackageResource>(this.action.href + '/execute', payload)
.toPromise()
.then((savedWp:WorkPackageResource) => {
this.wpNotificationsService.showSave(savedWp, false);
this.workPackage = savedWp;
this.wpCacheService.updateWorkPackage(savedWp);

@ -29,7 +29,7 @@ import {openprojectModule} from '../../angular-modules';
import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {Component, Inject, Input} from '@angular/core';
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {downgradeComponent} from '@angular/upgrade/static';
@Component({
@ -37,7 +37,7 @@ import {downgradeComponent} from '@angular/upgrade/static';
selector: 'wp-details-toolbar',
})
export class WorkPackageSplitViewToolbarComponent {
@Input('workPackage') workPackage:WorkPackageResourceInterface;
@Input('workPackage') workPackage:WorkPackageResource;
public text = {
button_more: this.I18n.t('js.button_more')

@ -27,9 +27,8 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {WorkPackageResource} from "../../api/api-v3/hal-resources/work-package-resource.service";
import {HalResource} from "core-components/api/api-v3/hal-resources/hal-resource.service";
import {TimezoneServiceToken} from 'core-app/angular4-transition-utils';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
export class DurationDisplayField extends DisplayField {

@ -27,7 +27,7 @@
// ++
import {DisplayField} from '../wp-display-field/wp-display-field.module';
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {$injectFields} from '../../angular/angular-injector-bridge.functions';
import * as angular from 'angular';

@ -28,7 +28,7 @@
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import ExpressionService from "../../common/xss/expression.service";
import {HalResource} from "../../api/api-v3/hal-resources/hal-resource.service";
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
export class FormattableDisplayField extends DisplayField {
protected ExpressionService:ExpressionService;

@ -27,7 +27,7 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {UiStateLinkBuilder} from '../../wp-fast-table/builders/ui-state-link-builder';
import {$stateToken} from 'core-app/angular4-transition-utils';
import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service';
@ -41,7 +41,7 @@ export class IdDisplayField extends DisplayField {
private $state:StateService = this.$injector.get($stateToken);
private keepTab:KeepTabService = this.$injector.get(KeepTabService);
constructor(public workPackage:WorkPackageResourceInterface,
constructor(public workPackage:WorkPackageResource,
public name:string,
public schema:op.FieldSchema) {
super(workPackage as any, name, schema);

@ -27,8 +27,8 @@
// ++
import {DisplayField} from "../wp-display-field/wp-display-field.module";
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {WorkPackageResourceInterface} from "core-components/api/api-v3/hal-resources/work-package-resource.service";
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
export const cssClassCustomOption = 'custom-option';

@ -26,10 +26,9 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {DurationDisplayField} from './wp-display-duration-field.module';
import {HalResource} from "core-components/api/api-v3/hal-resources/hal-resource.service";
import {PathHelperService} from 'core-components/common/path-helper/path-helper.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
export class SpentTimeDisplayField extends DurationDisplayField {
public template: string = '/components/wp-display/field-types/wp-display-spent-time-field.directive.html';

@ -26,13 +26,12 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {WorkPackageResource} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {UiStateLinkBuilder} from '../../wp-fast-table/builders/ui-state-link-builder';
import {DisplayField} from '../wp-display-field/wp-display-field.module';
import {HalResource} from 'core-components/api/api-v3/hal-resources/hal-resource.service';
import {StateService} from '@uirouter/core';
import {$stateToken} from 'core-app/angular4-transition-utils';
import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
export class WorkPackageDisplayField extends DisplayField {

@ -26,9 +26,9 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {HalResource} from '../../api/api-v3/hal-resources/hal-resource.service';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {Field, FieldFactory} from '../../wp-field/wp-field.module';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageChangeset} from '../../wp-edit-form/work-package-changeset';
import {Injector} from '@angular/core';
import {I18nToken} from 'core-app/angular4-transition-utils';
@ -102,7 +102,7 @@ export class DisplayFieldFactory extends FieldFactory {
protected static fields:{ [field:string]:string } = {};
protected static classes:{ [type:string]:typeof DisplayField } = {};
public static create(workPackage:WorkPackageResourceInterface,
public static create(workPackage:WorkPackageResource,
fieldName:string,
schema:op.FieldSchema):DisplayField {
let type = DisplayFieldFactory.getSpecificType(fieldName) ||

@ -1,7 +1,7 @@
import {ProgressTextDisplayField} from './../wp-display/field-types/wp-display-progress-text-field.module';
import {$injectFields} from '../angular/angular-injector-bridge.functions';
import {WorkPackageDisplayFieldService} from '../wp-display/wp-display-field/wp-display-field.service';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {DisplayField} from '../wp-display/wp-display-field/wp-display-field.module';
import {MultipleLinesStringObjectsDisplayField} from '../wp-display/field-types/wp-display-multiple-lines-string-objects-field.module';
import {WorkPackageChangeset} from './work-package-changeset';
@ -24,7 +24,7 @@ export class DisplayFieldRenderer {
$injectFields(this, 'wpDisplayField', 'I18n');
}
public render(workPackage:WorkPackageResourceInterface,
public render(workPackage:WorkPackageResource,
name:string,
changeset:WorkPackageChangeset|null,
placeholder = cellEmptyPlaceholder):HTMLSpanElement {
@ -39,7 +39,7 @@ export class DisplayFieldRenderer {
return span;
}
public renderFieldValue(workPackage:WorkPackageResourceInterface,
public renderFieldValue(workPackage:WorkPackageResource,
name:string,
changeset:WorkPackageChangeset|null,
placeholder = cellEmptyPlaceholder):[DisplayField|null, HTMLSpanElement] {
@ -65,7 +65,7 @@ export class DisplayFieldRenderer {
return [field, span];
}
public getField(workPackage:WorkPackageResourceInterface,
public getField(workPackage:WorkPackageResource,
fieldSchema:op.FieldSchema,
name:string,
changeset:WorkPackageChangeset|null):DisplayField {
@ -75,7 +75,7 @@ export class DisplayFieldRenderer {
return field;
}
private getFieldForCurrentContext(workPackage:WorkPackageResourceInterface, fieldSchema:op.FieldSchema, name:string) {
private getFieldForCurrentContext(workPackage:WorkPackageResource, fieldSchema:op.FieldSchema, name:string) {
// We handle multi value fields differently in the single view context
const isMultiLinesField = ['[]CustomOption', '[]User'].indexOf(fieldSchema.type) >= 0;
@ -99,7 +99,7 @@ export class DisplayFieldRenderer {
}
}
private setSpanAttributes(span:HTMLElement, field:DisplayField, name:string, workPackage:WorkPackageResourceInterface):void {
private setSpanAttributes(span:HTMLElement, field:DisplayField, name:string, workPackage:WorkPackageResource):void {
span.classList.add(cellClassName, displayClassName, 'inplace-edit', 'wp-edit-field', name);
span.dataset['fieldName'] = name;
@ -122,7 +122,7 @@ export class DisplayFieldRenderer {
}
}
private getAriaLabel(field:DisplayField, workPackage:WorkPackageResourceInterface):string {
private getAriaLabel(field:DisplayField, workPackage:WorkPackageResource):string {
let titleContent;
let labelContent = this.getLabelContent(field);

@ -30,7 +30,7 @@ import {StateService} from '@uirouter/core';
import {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive';
import {WorkPackageEditFieldComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field.component';
import {SimpleTemplateRenderer} from '../angular/simple-template-renderer';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {States} from '../states.service';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
@ -106,13 +106,13 @@ export class SingleViewEditContext implements WorkPackageEditContext {
handler.$scope.$evalAsync(() => handler.field = field);
}
public async reset(workPackage:WorkPackageResourceInterface, fieldName:string, focus:boolean = false) {
public async reset(workPackage:WorkPackageResource, fieldName:string, focus:boolean = false) {
const ctrl = await this.fieldCtrl(fieldName);
ctrl.reset(workPackage);
ctrl.deactivate(focus);
}
public onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResourceInterface) {
public onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResource) {
this.fieldGroup.onSaved(isInitial, savedWorkPackage);
}

@ -29,7 +29,8 @@
import {Injector} from '@angular/core';
import {$qToken, $timeoutToken, FocusHelperToken} from 'core-app/angular4-transition-utils';
import {SimpleTemplateRenderer} from '../angular/simple-template-renderer';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {injectorBridge} from '../angular/angular-injector-bridge.functions';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {States} from '../states.service';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {CellBuilder, editCellContainer, tdClassName} from '../wp-fast-table/builders/cell-builder';
@ -70,7 +71,7 @@ export class TableRowEditContext implements WorkPackageEditContext {
return this.rowContainer.find(`.${tdClassName}.${fieldName}`).first();
}
public activateField(form:WorkPackageEditForm, field:EditField, fieldName:string, errors:string[]):ng.IPromise<WorkPackageEditFieldHandler> {
public activateField(form:WorkPackageEditForm, field:EditField, fieldName:string, errors:string[]):Promise<WorkPackageEditFieldHandler> {
const deferred = this.$q.defer<WorkPackageEditFieldHandler>();
this.waitForContainer(fieldName)
@ -119,7 +120,7 @@ export class TableRowEditContext implements WorkPackageEditContext {
handler.$scope.$evalAsync(() => handler.field = field);
}
public reset(workPackage:WorkPackageResourceInterface, fieldName:string, focus?:boolean) {
public reset(workPackage:WorkPackageResource, fieldName:string, focus?:boolean) {
const cell = this.findContainer(fieldName);
if (cell.length) {
@ -142,7 +143,7 @@ export class TableRowEditContext implements WorkPackageEditContext {
return 'subject';
}
public onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResourceInterface) {
public onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResource) {
// Nothing to do here.
}
@ -167,8 +168,3 @@ export class TableRowEditContext implements WorkPackageEditContext {
return jQuery(`.${this.classIdentifier}-table`);
}
}
// TableRowEditContext.$inject = [
// 'wpCacheService', 'states', 'wpTableColumns', 'wpTableRefresh',
// 'FocusHelper', '$q', '$timeout', 'templateRenderer'
// ];

@ -28,18 +28,14 @@
import {input} from 'reactivestates';
import {debugLog} from '../../helpers/debug_output';
import {ErrorResource} from '../api/api-v3/hal-resources/error-resource.service';
import {FormResourceInterface} from '../api/api-v3/hal-resources/form-resource.service';
import {
WorkPackageResource,
WorkPackageResourceInterface
} from '../api/api-v3/hal-resources/work-package-resource.service';
import {SchemaCacheService} from '../schemas/schema-cache.service';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
import {WorkPackageCreateService} from '../wp-new/wp-create.service';
import {WorkPackageEditingService} from './work-package-editing-service';
import {Injector} from '@angular/core';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {FormResource} from 'core-app/modules/hal/resources/form-resource';
export class WorkPackageChangeset {
// Injections
@ -54,14 +50,14 @@ export class WorkPackageChangeset {
public inFlight:boolean = false;
// The current work package form
public wpForm = input<FormResourceInterface>();
public wpForm = input<FormResource>();
// The current editing resource
public resource:WorkPackageResourceInterface | null;
public resource:WorkPackageResource|null;
constructor(readonly injector:Injector,
public workPackage:WorkPackageResourceInterface,
form?:FormResourceInterface) {
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.putValue(form);
@ -124,8 +120,8 @@ export class WorkPackageChangeset {
return this.changes.hasOwnProperty(key);
}
public async getForm():Promise<FormResourceInterface> {
this.wpForm.putFromPromiseIfPristine(async() => {
public async getForm():Promise<FormResource> {
this.wpForm.putFromPromiseIfPristine(() => {
return this.updateForm();
});
@ -133,7 +129,7 @@ export class WorkPackageChangeset {
if (this.wpForm.hasValue()) {
return Promise.resolve(this.wpForm.value!);
} else {
return new Promise<FormResourceInterface>((resolve) => this.wpForm.valuesPromise().then(resolve));
return new Promise<FormResource>((resolve) => this.wpForm.valuesPromise().then(resolve));
}
}
@ -141,12 +137,12 @@ export class WorkPackageChangeset {
* Update the form resource from the API.
* @return {angular.IPromise<any>}
*/
public async updateForm():Promise<FormResourceInterface> {
public async updateForm():Promise<FormResource> {
let payload = this.buildPayloadFromChanges();
return new Promise<FormResourceInterface>((resolve, reject) => {
return new Promise<FormResource>((resolve, reject) => {
this.workPackage.$links.update(payload)
.then((form:FormResourceInterface) => {
.then((form:FormResource) => {
this.wpForm.putValue(form);
this.buildResource();
@ -160,25 +156,23 @@ export class WorkPackageChangeset {
});
}
public async save():Promise<WorkPackageResourceInterface> {
public async save():Promise<WorkPackageResource> {
this.inFlight = true;
const wasNew = this.workPackage.isNew;
let promise = new Promise<WorkPackageResourceInterface>((resolve, reject) => {
let promise = new Promise<WorkPackageResource>((resolve, reject) => {
this.updateForm()
.then((form) => {
const payload = this.buildPayloadFromChanges();
// Reject errors when occurring in form validation
const errors = ErrorResource.fromFormResponse(form);
const errors = form.getErrors();
if (errors !== null) {
return reject(errors);
}
this.workPackage.$links.updateImmediately(payload)
.then(async(savedWp:WorkPackageResourceInterface) => {
.then((savedWp:WorkPackageResource) => {
// Initialize any potentially new HAL values
this.workPackage.$initialize(savedWp);
@ -205,9 +199,9 @@ export class WorkPackageChangeset {
errorsOnForm: false,
error: error
});
});
})
.catch(reject);
})
.catch(reject);
});
});
promise
@ -220,7 +214,7 @@ export class WorkPackageChangeset {
/**
* Merge the current changes into the payload resource.
*
* @param {FormResourceInterface} form
* @param {FormResource} form
* @return {any}
*/
private mergeWithPayload(plainPayload:any) {
@ -328,14 +322,14 @@ export class WorkPackageChangeset {
let payload:any = this.workPackage.$plain();
if (hasForm) {
_.defaultsDeep(payload, this.wpForm.value!.payload.$source);
}
const resource = new WorkPackageResource(this.mergeWithPayload(payload), true);
// Override the schema with the current form, if any.
resource.overriddenSchema = this.schema;
this.resource = (resource as WorkPackageResourceInterface);
this.resource = (resource as WorkPackageResource);
this.wpEditing.updateValue(this.workPackage.id, this);
}
}

@ -26,10 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {
WorkPackageResource,
WorkPackageResourceInterface
} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageEditForm} from './work-package-edit-form';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageEditFieldHandler} from './work-package-edit-field-handler';
@ -52,7 +49,7 @@ export interface WorkPackageEditContext {
/**
* Reset the field and re-render the current WPs value.
*/
reset(workPackage:WorkPackageResourceInterface, fieldName:string, focus?:boolean):void;
reset(workPackage:WorkPackageResource, fieldName:string, focus?:boolean):void;
/**
* Return the first relevant field from the given list of attributes.
@ -62,5 +59,5 @@ export interface WorkPackageEditContext {
/**
* Optional callback when the form is being saved
*/
onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResourceInterface):void;
onSaved(isInitial:boolean, savedWorkPackage:WorkPackageResource):void;
}

@ -30,10 +30,6 @@ import {WorkPackageEditForm} from './work-package-edit-form';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageEditContext} from './work-package-edit-context';
import {$injectFields} from '../angular/angular-injector-bridge.functions';
import {
WorkPackageResource,
WorkPackageResourceInterface
} from '../api/api-v3/hal-resources/work-package-resource.service';
import {keyCodes} from '../common/keyCodes.enum';
export class WorkPackageEditFieldHandler {

@ -27,8 +27,8 @@
// ++
import {Subscription} from 'rxjs/Subscription';
import {$injectFields, injectorBridge} from '../angular/angular-injector-bridge.functions';
import {ErrorResource} from '../api/api-v3/hal-resources/error-resource.service';
import {$injectFields} from '../angular/angular-injector-bridge.functions';
import {ErrorResource} from 'core-app/modules/hal/resources/error-resource';
import {States} from '../states.service';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
@ -38,9 +38,9 @@ import {WorkPackageEditContext} from './work-package-edit-context';
import {WorkPackageEditFieldHandler} from './work-package-edit-field-handler';
import {debugLog} from '../../helpers/debug_output';
import {WorkPackageChangeset} from './work-package-changeset';
import {FormResourceInterface} from '../api/api-v3/hal-resources/form-resource.service';
import {FormResource} from 'core-app/modules/hal/resources/form-resource';
import {WorkPackageEditingService} from './work-package-editing-service';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageTableRefreshService} from '../wp-table/wp-table-refresh-request.service';
import {Injector} from '@angular/core';
@ -71,7 +71,7 @@ export class WorkPackageEditForm {
public static createInContext(injector:Injector,
editContext:WorkPackageEditContext,
wp:WorkPackageResourceInterface,
wp:WorkPackageResource,
editMode:boolean = false) {
const form = new WorkPackageEditForm(injector, wp, editMode);
@ -81,12 +81,12 @@ export class WorkPackageEditForm {
}
constructor(readonly injector:Injector,
public workPackage:WorkPackageResourceInterface,
public workPackage:WorkPackageResource,
public editMode:boolean = false) {
this.wpSubscription = this.wpCacheService.state(workPackage.id)
.values$()
.subscribe((wp:WorkPackageResourceInterface) => {
.subscribe((wp:WorkPackageResource) => {
this.workPackage = wp;
});
@ -179,7 +179,7 @@ export class WorkPackageEditForm {
* Save the active changeset.
* @return {any}
*/
public async submit():Promise<WorkPackageResourceInterface> {
public async submit():Promise<WorkPackageResource> {
if (this.changeset.empty && !this.workPackage.isNew) {
this.closeEditFields();
return Promise.resolve(this.workPackage);
@ -194,7 +194,7 @@ export class WorkPackageEditForm {
const openFields = _.keys(this.activeFields);
_.each(this.activeFields, (handler:WorkPackageEditFieldHandler) => handler.field.onSubmit());
return new Promise<WorkPackageResourceInterface>((resolve, reject) => {
return new Promise<WorkPackageResource>((resolve, reject) => {
this.changeset.save()
.then(savedWorkPackage => {
// Close all current fields
@ -297,7 +297,7 @@ export class WorkPackageEditForm {
private async buildField(fieldName:string):Promise<EditField> {
return new Promise<EditField>((resolve, reject) => {
this.changeset.getForm()
.then((form:FormResourceInterface) => {
.then((form:FormResource) => {
const schemaName = this.changeset.getSchemaName(fieldName);
const fieldSchema = form.schema[schemaName];

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save