Feat/frontend linting (#9424)

* Change eslintrc config

* Try esprint fix

* Run linter fix

* Set most of the typescript linting rules to warn, run with --fix

* Fix some linting errors

* Optimize imports

* Build works again

* Remove fixes that didn't fix anything

* Make imports lint-conform again && disable trailing underscores as it is part of Angular and our convention

* Remove wrong automated fix

* Rename components with suffix "Component" for linting

* Linting, refactor reorder-delta-builder to a more functional style

* Update delta reorder specs

* Add exceptions for "++" in loops and bracket expressions in arrow functions

* Some linting fixes

* Fix some more linting

* Optimize imports

Co-authored-by: Henriette Darge <h.darge@openproject.com>
pull/9438/head
Benjamin Bädorf 3 years ago committed by GitHub
parent 89bd15516d
commit 3a877457ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 136
      frontend/.eslintrc.js
  2. 10
      frontend/.esprintrc
  3. 20289
      frontend/npm-shrinkwrap.json
  4. 8
      frontend/package.json
  5. 146
      frontend/src/app/app.module.ts
  6. 13
      frontend/src/app/core/active-window/active-window.service.ts
  7. 36
      frontend/src/app/core/apiv3/api-v3.service.spec.ts
  8. 69
      frontend/src/app/core/apiv3/api-v3.service.ts
  9. 43
      frontend/src/app/core/apiv3/cache/cachable-apiv3-collection.ts
  10. 48
      frontend/src/app/core/apiv3/cache/cachable-apiv3-resource.ts
  11. 26
      frontend/src/app/core/apiv3/cache/state-cache.service.ts
  12. 28
      frontend/src/app/core/apiv3/endpoints/capabilities/apiv3-capabilities-paths.ts
  13. 10
      frontend/src/app/core/apiv3/endpoints/capabilities/apiv3-capability-paths.ts
  14. 15
      frontend/src/app/core/apiv3/endpoints/capabilities/capability-cache.service.ts
  15. 20
      frontend/src/app/core/apiv3/endpoints/configuration/apiv3-configuration-path.ts
  16. 36
      frontend/src/app/core/apiv3/endpoints/grids/apiv3-grid-form.ts
  17. 12
      frontend/src/app/core/apiv3/endpoints/grids/apiv3-grid-paths.ts
  18. 30
      frontend/src/app/core/apiv3/endpoints/grids/apiv3-grids-paths.ts
  19. 12
      frontend/src/app/core/apiv3/endpoints/groups/apiv3-group-paths.ts
  20. 26
      frontend/src/app/core/apiv3/endpoints/groups/apiv3-groups-paths.ts
  21. 14
      frontend/src/app/core/apiv3/endpoints/help_texts/apiv3-help-texts-paths.ts
  22. 21
      frontend/src/app/core/apiv3/endpoints/memberships/apiv3-memberships-form.ts
  23. 33
      frontend/src/app/core/apiv3/endpoints/memberships/apiv3-memberships-paths.ts
  24. 21
      frontend/src/app/core/apiv3/endpoints/news/apiv3-news-paths.ts
  25. 24
      frontend/src/app/core/apiv3/endpoints/notifications/apiv3-notification-paths.ts
  26. 39
      frontend/src/app/core/apiv3/endpoints/notifications/apiv3-notifications-paths.ts
  27. 12
      frontend/src/app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-user-paths.ts
  28. 26
      frontend/src/app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-users-paths.ts
  29. 29
      frontend/src/app/core/apiv3/endpoints/projects/apiv3-available-projects-paths.ts
  30. 10
      frontend/src/app/core/apiv3/endpoints/projects/apiv3-project-copy-paths.ts
  31. 20
      frontend/src/app/core/apiv3/endpoints/projects/apiv3-project-paths.ts
  32. 28
      frontend/src/app/core/apiv3/endpoints/projects/apiv3-projects-paths.ts
  33. 12
      frontend/src/app/core/apiv3/endpoints/projects/project.cache.ts
  34. 44
      frontend/src/app/core/apiv3/endpoints/queries/apiv3-queries-paths.ts
  35. 45
      frontend/src/app/core/apiv3/endpoints/queries/apiv3-query-form.ts
  36. 28
      frontend/src/app/core/apiv3/endpoints/queries/apiv3-query-order.ts
  37. 23
      frontend/src/app/core/apiv3/endpoints/queries/apiv3-query-paths.ts
  38. 22
      frontend/src/app/core/apiv3/endpoints/relations/apiv3-relations-paths.ts
  39. 9
      frontend/src/app/core/apiv3/endpoints/roles/apiv3-role-paths.ts
  40. 24
      frontend/src/app/core/apiv3/endpoints/roles/apiv3-roles-paths.ts
  41. 9
      frontend/src/app/core/apiv3/endpoints/statuses/apiv3-status-paths.ts
  42. 24
      frontend/src/app/core/apiv3/endpoints/statuses/apiv3-statuses-paths.ts
  43. 32
      frontend/src/app/core/apiv3/endpoints/time-entries/apiv3-time-entries-paths.ts
  44. 31
      frontend/src/app/core/apiv3/endpoints/time-entries/apiv3-time-entry-paths.ts
  45. 17
      frontend/src/app/core/apiv3/endpoints/time-entries/time-entry-cache.service.ts
  46. 11
      frontend/src/app/core/apiv3/endpoints/types/apiv3-type-paths.ts
  47. 15
      frontend/src/app/core/apiv3/endpoints/types/apiv3-types-paths.ts
  48. 11
      frontend/src/app/core/apiv3/endpoints/users/apiv3-user-paths.ts
  49. 25
      frontend/src/app/core/apiv3/endpoints/users/apiv3-user-preferences-paths.ts
  50. 22
      frontend/src/app/core/apiv3/endpoints/users/apiv3-users-paths.ts
  51. 22
      frontend/src/app/core/apiv3/endpoints/versions/apiv3-version-paths.ts
  52. 22
      frontend/src/app/core/apiv3/endpoints/versions/apiv3-versions-paths.ts
  53. 30
      frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-package-cached-subresource.ts
  54. 13
      frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-package-paths.ts
  55. 56
      frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths.ts
  56. 12
      frontend/src/app/core/apiv3/endpoints/work_packages/apiv3-work-package-form.ts
  57. 40
      frontend/src/app/core/apiv3/endpoints/work_packages/work-package-cache.spec.ts
  58. 17
      frontend/src/app/core/apiv3/endpoints/work_packages/work-package.cache.ts
  59. 18
      frontend/src/app/core/apiv3/forms/apiv3-form-resource.ts
  60. 6
      frontend/src/app/core/apiv3/openproject-api-v3.module.ts
  61. 8
      frontend/src/app/core/apiv3/paths/apiv3-list-resource.interface.ts
  62. 42
      frontend/src/app/core/apiv3/paths/apiv3-resource.ts
  63. 5
      frontend/src/app/core/apiv3/paths/path-resources.ts
  64. 2
      frontend/src/app/core/apiv3/types/hal-collection.type.ts
  65. 33
      frontend/src/app/core/apiv3/virtual/apiv3-board-path.ts
  66. 67
      frontend/src/app/core/apiv3/virtual/apiv3-boards-paths.ts
  67. 6
      frontend/src/app/core/augmenting/dynamic-scripts/backlogs/model.js
  68. 84
      frontend/src/app/core/augmenting/dynamic-scripts/global_roles.ts
  69. 49
      frontend/src/app/core/augmenting/dynamic-scripts/two_factor_authentication.ts
  70. 13
      frontend/src/app/core/augmenting/openproject-augmenting.module.ts
  71. 14
      frontend/src/app/core/augmenting/services/path-script.augment.service.ts
  72. 32
      frontend/src/app/core/backup/op-backup.service.ts
  73. 8
      frontend/src/app/core/browser/browser-detector.service.ts
  74. 1
      frontend/src/app/core/browser/device.service.ts
  75. 14
      frontend/src/app/core/config/configuration.service.ts
  76. 14
      frontend/src/app/core/current-project/current-project.service.spec.ts
  77. 12
      frontend/src/app/core/current-project/current-project.service.ts
  78. 10
      frontend/src/app/core/current-user/current-user.module.ts
  79. 15
      frontend/src/app/core/current-user/current-user.query.ts
  80. 20
      frontend/src/app/core/current-user/current-user.service.spec.ts
  81. 142
      frontend/src/app/core/current-user/current-user.service.ts
  82. 16
      frontend/src/app/core/current-user/current-user.store.ts
  83. 36
      frontend/src/app/core/datetime/timezone.service.spec.ts
  84. 38
      frontend/src/app/core/datetime/timezone.service.ts
  85. 11
      frontend/src/app/core/enterprise/banners.service.ts
  86. 6
      frontend/src/app/core/errors/sentry/sentry-dependency.ts
  87. 35
      frontend/src/app/core/errors/sentry/sentry-reporter.ts
  88. 3
      frontend/src/app/core/expression/expression.service.ts
  89. 74
      frontend/src/app/core/file-upload/op-direct-file-upload.service.ts
  90. 28
      frontend/src/app/core/file-upload/op-file-upload.service.spec.ts
  91. 67
      frontend/src/app/core/file-upload/op-file-upload.service.ts
  92. 101
      frontend/src/app/core/forms/forms.service.spec.ts
  93. 106
      frontend/src/app/core/forms/forms.service.ts
  94. 21
      frontend/src/app/core/forms/typings.d.ts
  95. 6
      frontend/src/app/core/global_search/global-search-work-packages-entry.component.ts
  96. 74
      frontend/src/app/core/global_search/global-search-work-packages.component.ts
  97. 121
      frontend/src/app/core/global_search/input/global-search-input.component.ts
  98. 23
      frontend/src/app/core/global_search/openproject-global-search.module.ts
  99. 32
      frontend/src/app/core/global_search/services/global-search.service.spec.ts
  100. 31
      frontend/src/app/core/global_search/services/global-search.service.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,8 +1,6 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
env: {
browser: true,
@ -19,19 +17,23 @@ module.exports = {
],
overrides: [
{
"files": ["*.ts"],
"parserOptions": {
"project": [
"./src/tsconfig.app.json"
],
"createDefaultProgram": true
files: ["*.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./src/tsconfig.app.json",
tsconfigRootDir: __dirname,
sourceType: "module",
createDefaultProgram: true
},
"extends": [
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:@angular-eslint/recommended",
// This is required if you use inline templates in Components
"plugin:@angular-eslint/template/process-inline-templates"
"plugin:@angular-eslint/template/process-inline-templates",
"airbnb-typescript",
],
"rules": {
rules: {
/**
* Any TypeScript source code (NOT TEMPLATE) related rules you wish to use/reconfigure over and above the
* recommended set provided by the @angular-eslint project would go here.
@ -44,97 +46,32 @@ module.exports = {
"error",
{ "type": "element", "prefix": "op", "style": "kebab-case" }
],
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-empty-function": "error",
// note you must disable the base rule as it can report incorrect errors
semi: "off",
"@typescript-eslint/semi": ["error"],
"brace-style": [
"error",
"1tbs",
],
curly: "error",
"eol-last": "off",
eqeqeq: [
"error",
"smart",
],
"guard-for-in": "error",
"id-blacklist": "off",
"id-match": "off",
"max-len": [
"off",
{
code: 140,
},
],
"no-bitwise": "off",
"no-caller": "error",
"no-console": [
"error",
{
allow: [
"log",
"warn",
"dir",
"timeLog",
"assert",
"clear",
"count",
"countReset",
"group",
"groupEnd",
"table",
"dirxml",
"error",
"groupCollapsed",
"Console",
"profile",
"profileEnd",
"timeStamp",
"context",
],
},
],
"no-debugger": "error",
"no-empty": "error",
"no-eval": "error",
"no-new-wrappers": "error",
"no-redeclare": "error",
"no-trailing-spaces": "error",
"no-underscore-dangle": "off",
"no-unused-labels": "error",
"no-var": "off",
radix: "off",
// Disable required spaces in license comments
"spaced-comment": "off",
// Disable preference on quotes, rely on formatter instead
quotes: "off",
// Who cares about line length
"max-len": "off",
// Disable consistent return as typescript checks return type
"consistent-return": "off",
// Disable forcing arrow function params for one
"arrow-parens": "off",
// Disable enforce class methods use this
"class-methods-use-this": "off",
// Force single quotes to align with ruby
quotes: "off",
"@typescript-eslint/quotes": ["error", "single", { avoidEscape: true }],
// Disable webpack loader definitions
"import/no-webpack-loader-syntax": "off",
/*
// Disable use before define, as irrelevant for TS interfaces
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
// Allow object.hasOwnProperty calls
"no-prototype-builtins": "off",
// We need to redeclare interface with the same name
// as a class or constant for type ducking
"no-redeclare": "off",
*/
// Whitespace configuration
"@typescript-eslint/type-annotation-spacing": [
@ -154,17 +91,34 @@ module.exports = {
// Allow empty interfaces for naming purposes (HAL resources)
"@typescript-eslint/no-empty-interface": "off",
// Force spaces in objects
"object-curly-spacing": ["error", "always"],
"import/prefer-default-export": "off",
"no-underscore-dangle": "warn",
"no-return-assign": ["error", "except-parens"],
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
//////////////////////////////////////////////////////////////////////
// Anything below this line should be turned on again at some point //
//////////////////////////////////////////////////////////////////////
// It's common in Angular to wrap even pure functions in classes for injection purposes
// TODO: Should probably be turned off and pure unit tests should be used at some point
"class-methods-use-this": "warn",
// There's too much interop with legacy code that is `any`-typed for this to be an error in any practical sense
// TODO: Actually type everything
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-call": "warn",
// Force indent to 2space
indent: ["error", 2],
// This is probably the first rule that should be fixed. It had 309 errors last time we checked
"@typescript-eslint/no-unsafe-return": "warn",
}
},
{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended"],
"rules": {
files: ["*.html"],
extends: ["plugin:@angular-eslint/template/recommended"],
rules: {
/**
* Any template/HTML related rules you wish to use/reconfigure over and above the
* recommended set provided by the @angular-eslint project would go here.

@ -0,0 +1,10 @@
{
"paths": ["src/**/*.ts", "src/*.ts"],
"ignored": [
"**/node_modules/**/*",
"src/**/*.spec.ts",
"src/test/*"
],
"port": 5004,
"quiet": true
}

File diff suppressed because it is too large Load Diff

@ -11,6 +11,8 @@
"@angular-eslint/schematics": "12.0.0",
"@angular-eslint/template-parser": "^12.0.0",
"@angular/language-service": "12.0.2",
"@html-eslint/eslint-plugin": "^0.11.0",
"@html-eslint/parser": "^0.11.0",
"@jsdevtools/coverage-istanbul-loader": "3.0.5",
"@types/jasmine": "~3.6.0",
"@types/swagger-ui": "^3.47.0",
@ -19,7 +21,12 @@
"codelyzer": "^6.0.0",
"eslint": "^7.26.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"esprint": "^3.1.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.2",
@ -132,6 +139,7 @@
"test": "ng test --watch=false",
"test-watch": "ng test --watch=true",
"lint": "eslint -c .eslintrc.js --ext .ts src/app/",
"lint:fix": "esprint check --fix",
"generate-typings": "tsc -d -p src/tsconfig.app.json"
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,60 +26,78 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APP_INITIALIZER, ApplicationRef, Injector, NgModule } from '@angular/core';
import {
APP_INITIALIZER, ApplicationRef, Injector, NgModule,
} from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive';
import { States } from 'core-app/core/states/states.service';
import { OpenprojectFieldsModule } from "core-app/shared/components/fields/openproject-fields.module";
import { OPSharedModule } from "core-app/shared/shared.module";
import { OpDragScrollDirective } from "core-app/shared/directives/op-drag-scroll/op-drag-scroll.directive";
import { OpenProjectDirectFileUploadService } from './core/file-upload/op-direct-file-upload.service';
import { DynamicBootstrapper } from "core-app/core/setup/globals/dynamic-bootstrapper";
import { OpenprojectFieldsModule } from 'core-app/shared/components/fields/openproject-fields.module';
import { OPSharedModule } from 'core-app/shared/shared.module';
import { OpDragScrollDirective } from 'core-app/shared/directives/op-drag-scroll/op-drag-scroll.directive';
import { DynamicBootstrapper } from 'core-app/core/setup/globals/dynamic-bootstrapper';
import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module';
import { OpenprojectAttachmentsModule } from 'core-app/shared/components/attachments/openproject-attachments.module';
import { OpenprojectEditorModule } from 'core-app/shared/components/editor/openproject-editor.module';
import { OpenprojectGridsModule } from "core-app/shared/components/grids/openproject-grids.module";
import { OpenprojectRouterModule } from "core-app/core/routing/openproject-router.module";
import { OpenprojectWorkPackageRoutesModule } from "core-app/features/work-packages/openproject-work-package-routes.module";
import { BrowserModule } from "@angular/platform-browser";
import { OpenprojectCalendarModule } from "core-app/shared/components/calendar/openproject-calendar.module";
import { OpenprojectGlobalSearchModule } from "core-app/core/global_search/openproject-global-search.module";
import { OpenprojectDashboardsModule } from "core-app/features/dashboards/openproject-dashboards.module";
import { OpenprojectWorkPackageGraphsModule } from "core-app/shared/components/work-package-graphs/openproject-work-package-graphs.module";
import { PreviewTriggerService } from "core-app/core/setup/globals/global-listeners/preview-trigger.service";
import { OpenprojectOverviewModule } from "core-app/features/overview/openproject-overview.module";
import { OpenprojectMyPageModule } from "core-app/features/my-page/openproject-my-page.module";
import { OpenprojectProjectsModule } from "core-app/features/projects/openproject-projects.module";
import { KeyboardShortcutService } from "core-app/shared/directives/a11y/keyboard-shortcut-service";
import { OpenprojectMembersModule } from "core-app/shared/components/autocompleter/members-autocompleter/members.module";
import { OpenprojectAugmentingModule } from "core-app/core/augmenting/openproject-augmenting.module";
import { OpenprojectInviteUserModalModule } from "core-app/features/invite-user-modal/invite-user-modal.module";
import { OpenprojectModalModule } from "core-app/shared/components/modal/modal.module";
import { RevitAddInSettingsButtonService } from "core-app/features/bim/revit_add_in/revit-add-in-settings-button.service";
import { OpenprojectAutocompleterModule } from "core-app/shared/components/autocompleter/openproject-autocompleter.module";
import { OpenprojectGridsModule } from 'core-app/shared/components/grids/openproject-grids.module';
import { OpenprojectRouterModule } from 'core-app/core/routing/openproject-router.module';
import { OpenprojectWorkPackageRoutesModule } from 'core-app/features/work-packages/openproject-work-package-routes.module';
import { BrowserModule } from '@angular/platform-browser';
import { OpenprojectCalendarModule } from 'core-app/shared/components/calendar/openproject-calendar.module';
import { OpenprojectGlobalSearchModule } from 'core-app/core/global_search/openproject-global-search.module';
import { OpenprojectDashboardsModule } from 'core-app/features/dashboards/openproject-dashboards.module';
import { OpenprojectWorkPackageGraphsModule } from 'core-app/shared/components/work-package-graphs/openproject-work-package-graphs.module';
import { PreviewTriggerService } from 'core-app/core/setup/globals/global-listeners/preview-trigger.service';
import { OpenprojectOverviewModule } from 'core-app/features/overview/openproject-overview.module';
import { OpenprojectMyPageModule } from 'core-app/features/my-page/openproject-my-page.module';
import { OpenprojectProjectsModule } from 'core-app/features/projects/openproject-projects.module';
import { KeyboardShortcutService } from 'core-app/shared/directives/a11y/keyboard-shortcut-service';
import { OpenprojectMembersModule } from 'core-app/shared/components/autocompleter/members-autocompleter/members.module';
import { OpenprojectAugmentingModule } from 'core-app/core/augmenting/openproject-augmenting.module';
import { OpenprojectInviteUserModalModule } from 'core-app/features/invite-user-modal/invite-user-modal.module';
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
import { RevitAddInSettingsButtonService } from 'core-app/features/bim/revit_add_in/revit-add-in-settings-button.service';
import { OpenprojectAutocompleterModule } from 'core-app/shared/components/autocompleter/openproject-autocompleter.module';
import { OpenProjectFileUploadService } from 'core-app/core/file-upload/op-file-upload.service';
import { OpenprojectEnterpriseModule } from 'core-app/features/enterprise/openproject-enterprise.module';
import { MainMenuToggleComponent } from 'core-app/core/main-menu/main-menu-toggle.component';
import { MainMenuNavigationService } from 'core-app/core/main-menu/main-menu-navigation.service';
import { ConfirmDialogService } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.service';
import { ConfirmDialogModalComponent } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.modal';
import { DynamicContentModalComponent } from 'core-app/shared/components/modals/modal-wrapper/dynamic-content.modal';
import { PasswordConfirmationModalComponent } from 'core-app/shared/components/modals/request-for-confirmation/password-confirmation.modal';
import { WpPreviewModalComponent } from 'core-app/shared/components/modals/preview-modal/wp-preview-modal/wp-preview.modal';
import { ConfirmFormSubmitController } from 'core-app/shared/components/modals/confirm-form-submit/confirm-form-submit.directive';
import { ProjectMenuAutocompleteComponent } from 'core-app/shared/components/autocompleter/project-menu-autocomplete/project-menu-autocomplete.component';
import { PaginationService } from 'core-app/shared/components/table-pagination/pagination-service';
import { MainMenuResizerComponent } from 'core-app/shared/components/resizer/resizer/main-menu-resizer.component';
import { CommentService } from 'core-app/features/work-packages/components/wp-activity/comment-service';
import { OpenprojectTabsModule } from 'core-app/shared/components/tabs/openproject-tabs.module';
import { OpenprojectAdminModule } from 'core-app/features/admin/openproject-admin.module';
import { OpenprojectHalModule } from 'core-app/features/hal/openproject-hal.module';
import { globalDynamicComponents } from 'core-app/core/setup/global-dynamic-components.const';
import { HookService } from 'core-app/features/plugins/hook-service';
import { OpenprojectPluginsModule } from 'core-app/features/plugins/openproject-plugins.module';
import { LinkedPluginsModule } from 'core-app/features/plugins/linked-plugins.module';
import { OpenProjectInAppNotificationsModule } from 'core-app/features/in-app-notifications/in-app-notifications.module';
import { OpenProjectBackupService } from './core/backup/op-backup.service';
import { OpenProjectFileUploadService } from "core-app/core/file-upload/op-file-upload.service";
import { OpenprojectEnterpriseModule } from "core-app/features/enterprise/openproject-enterprise.module";
import { MainMenuToggleComponent } from "core-app/core/main-menu/main-menu-toggle.component";
import { MainMenuNavigationService } from "core-app/core/main-menu/main-menu-navigation.service";
import { ConfirmDialogService } from "core-app/shared/components/modals/confirm-dialog/confirm-dialog.service";
import { ConfirmDialogModal } from "core-app/shared/components/modals/confirm-dialog/confirm-dialog.modal";
import { DynamicContentModal } from "core-app/shared/components/modals/modal-wrapper/dynamic-content.modal";
import { PasswordConfirmationModal } from "core-app/shared/components/modals/request-for-confirmation/password-confirmation.modal";
import { WpPreviewModal } from "core-app/shared/components/modals/preview-modal/wp-preview-modal/wp-preview.modal";
import { ConfirmFormSubmitController } from "core-app/shared/components/modals/confirm-form-submit/confirm-form-submit.directive";
import { ProjectMenuAutocompleteComponent } from "core-app/shared/components/autocompleter/project-menu-autocomplete/project-menu-autocomplete.component";
import { PaginationService } from "core-app/shared/components/table-pagination/pagination-service";
import { MainMenuResizerComponent } from "core-app/shared/components/resizer/resizer/main-menu-resizer.component";
import { CommentService } from "core-app/features/work-packages/components/wp-activity/comment-service";
import { OpenprojectTabsModule } from "core-app/shared/components/tabs/openproject-tabs.module";
import { OpenprojectAdminModule } from "core-app/features/admin/openproject-admin.module";
import { OpenprojectHalModule } from "core-app/features/hal/openproject-hal.module";
import { globalDynamicComponents } from "core-app/core/setup/global-dynamic-components.const";
import { HookService } from "core-app/features/plugins/hook-service";
import { OpenprojectPluginsModule } from "core-app/features/plugins/openproject-plugins.module";
import { LinkedPluginsModule } from "core-app/features/plugins/linked-plugins.module";
import { OpenProjectInAppNotificationsModule } from "core-app/features/in-app-notifications/in-app-notifications.module";
import { OpenProjectDirectFileUploadService } from './core/file-upload/op-direct-file-upload.service';
export function initializeServices(injector:Injector) {
return () => {
const PreviewTrigger = injector.get(PreviewTriggerService);
const mainMenuNavigationService = injector.get(MainMenuNavigationService);
const keyboardShortcuts = injector.get(KeyboardShortcutService);
// Conditionally add the Revit Add-In settings button
injector.get(RevitAddInSettingsButtonService);
mainMenuNavigationService.register();
PreviewTrigger.setupListener();
keyboardShortcuts.register();
};
}
@NgModule({
imports: [
@ -159,7 +177,9 @@ import { OpenProjectInAppNotificationsModule } from "core-app/features/in-app-no
],
providers: [
{ provide: States, useValue: new States() },
{ provide: APP_INITIALIZER, useFactory: initializeServices, deps: [Injector], multi: true },
{
provide: APP_INITIALIZER, useFactory: initializeServices, deps: [Injector], multi: true,
},
PaginationService,
OpenProjectBackupService,
OpenProjectFileUploadService,
@ -173,10 +193,10 @@ import { OpenProjectInAppNotificationsModule } from "core-app/features/in-app-no
OpContextMenuTrigger,
// Modals
ConfirmDialogModal,
DynamicContentModal,
PasswordConfirmationModal,
WpPreviewModal,
ConfirmDialogModalComponent,
DynamicContentModalComponent,
PasswordConfirmationModalComponent,
WpPreviewModalComponent,
// Main menu
MainMenuResizerComponent,
@ -188,13 +208,11 @@ import { OpenProjectInAppNotificationsModule } from "core-app/features/in-app-no
// Form configuration
OpDragScrollDirective,
ConfirmFormSubmitController,
]
],
})
export class OpenProjectModule {
// noinspection JSUnusedGlobalSymbols
ngDoBootstrap(appRef:ApplicationRef) {
// Register global dynamic components
// this is necessary to ensure they are not tree-shaken
// (if they are not used anywhere in Angular, they would be removed)
@ -214,19 +232,3 @@ export class OpenProjectModule {
});
}
}
export function initializeServices(injector:Injector) {
return () => {
const PreviewTrigger = injector.get(PreviewTriggerService);
const mainMenuNavigationService = injector.get(MainMenuNavigationService);
const keyboardShortcuts = injector.get(KeyboardShortcutService);
// Conditionally add the Revit Add-In settings button
injector.get(RevitAddInSettingsButtonService);
mainMenuNavigationService.register();
PreviewTrigger.setupListener();
keyboardShortcuts.register();
};
}

@ -1,17 +1,16 @@
import { Inject, Injectable } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { BehaviorSubject, Observable, Subject } from "rxjs";
import { debugLog } from "core-app/shared/helpers/debug_output";
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { BehaviorSubject, Observable } from 'rxjs';
import { debugLog } from 'core-app/shared/helpers/debug_output';
@Injectable({ providedIn: 'root' })
export class ActiveWindowService {
private activeState$ = new BehaviorSubject<boolean>(true);
constructor(@Inject(DOCUMENT) document:Document) {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState) {
debugLog("Browser window has visibility state changed to " + document.visibilityState);
debugLog(`Browser window has visibility state changed to ${document.visibilityState}`);
this.activeState$.next(document.visibilityState === 'visible');
}
});
@ -30,4 +29,4 @@ export class ActiveWindowService {
public get active$():Observable<boolean> {
return this.activeState$.asObservable();
}
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,12 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { TestBed, waitForAsync } from "@angular/core/testing";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { States } from "core-app/core/states/states.service";
import { TestBed, waitForAsync } from '@angular/core/testing';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { States } from 'core-app/core/states/states.service';
describe('APIv3Service', function() {
describe('APIv3Service', () => {
let service:APIV3Service;
beforeEach(waitForAsync(() => {
@ -40,8 +40,8 @@ describe('APIv3Service', function() {
providers: [
States,
PathHelperService,
APIV3Service
]
APIV3Service,
],
})
.compileComponents()
.then(() => {
@ -53,34 +53,34 @@ describe('APIv3Service', function() {
return new URLSearchParams(object).toString();
}
describe('apiV3', function() {
var projectIdentifier = 'majora';
describe('apiV3', () => {
const projectIdentifier = 'majora';
it('should provide the project\'s path', function() {
it("should provide the project's path", () => {
expect(service.projects.id(projectIdentifier).path).toEqual('/api/v3/projects/majora');
});
it('should provide a path to work package query on subject or ID ', function() {
it('should provide a path to work package query on subject or ID ', () => {
let params = {
filters: '[{"subjectOrId":{"operator":"**","values":["bogus"]}}]',
sortBy: '[["updatedAt","desc"]]',
offset: '1',
pageSize: '10'
pageSize: '10',
};
expect(
service.work_packages.filterBySubjectOrId("bogus").path
).toEqual('/api/v3/work_packages?' + encodeParams(params));
service.work_packages.filterBySubjectOrId('bogus').path,
).toEqual(`/api/v3/work_packages?${encodeParams(params)}`);
params = {
filters: '[{"id":{"operator":"=","values":["1234"]}}]',
sortBy: '[["updatedAt","desc"]]',
offset: '1',
pageSize: '10'
pageSize: '10',
};
expect(
service.work_packages.filterBySubjectOrId("1234", true).path
).toEqual('/api/v3/work_packages?' + encodeParams(params));
service.work_packages.filterBySubjectOrId('1234', true).path,
).toEqual(`/api/v3/work_packages?${encodeParams(params)}`);
});
});
});

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,39 +26,33 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable, Injector } from "@angular/core";
import {
APIv3GettableResource,
APIv3ResourceCollection,
APIv3ResourcePath
} from "core-app/core/apiv3/paths/apiv3-resource";
import { Constructor } from "@angular/cdk/table";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { Apiv3GridsPaths } from "core-app/core/apiv3/endpoints/grids/apiv3-grids-paths";
import { Apiv3TimeEntriesPaths } from "core-app/core/apiv3/endpoints/time-entries/apiv3-time-entries-paths";
import { Apiv3CapabilitiesPaths } from "core-app/core/apiv3/endpoints/capabilities/apiv3-capabilities-paths";
import { Apiv3MembershipsPaths } from "core-app/core/apiv3/endpoints/memberships/apiv3-memberships-paths";
import { Apiv3UsersPaths } from "core-app/core/apiv3/endpoints/users/apiv3-users-paths";
import { APIv3TypesPaths } from "core-app/core/apiv3/endpoints/types/apiv3-types-paths";
import { APIv3QueriesPaths } from "core-app/core/apiv3/endpoints/queries/apiv3-queries-paths";
import { APIV3WorkPackagesPaths } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths";
import { APIv3ProjectPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-project-paths";
import { APIv3ProjectsPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-projects-paths";
import { APIv3StatusesPaths } from "core-app/core/apiv3/endpoints/statuses/apiv3-statuses-paths";
import { APIv3RolesPaths } from "core-app/core/apiv3/endpoints/roles/apiv3-roles-paths";
import { APIv3VersionsPaths } from "core-app/core/apiv3/endpoints/versions/apiv3-versions-paths";
import { Apiv3RelationsPaths } from "core-app/core/apiv3/endpoints/relations/apiv3-relations-paths";
import { Apiv3NewsPaths } from "core-app/core/apiv3/endpoints/news/apiv3-news-paths";
import { Apiv3HelpTextsPaths } from "core-app/core/apiv3/endpoints/help_texts/apiv3-help-texts-paths";
import { Apiv3ConfigurationPath } from "core-app/core/apiv3/endpoints/configuration/apiv3-configuration-path";
import { Apiv3BoardsPaths } from "core-app/core/apiv3/virtual/apiv3-boards-paths";
import { RootResource } from "core-app/features/hal/resources/root-resource";
import * as ts from "typescript/lib/tsserverlibrary";
import Project = ts.server.Project;
import { Apiv3PlaceholderUsersPaths } from "core-app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-users-paths";
import { Apiv3GroupsPaths } from "core-app/core/apiv3/endpoints/groups/apiv3-groups-paths";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { Apiv3NotificationsPaths } from "core-app/core/apiv3/endpoints/notifications/apiv3-notifications-paths";
import { Injectable, Injector } from '@angular/core';
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Constructor } from '@angular/cdk/table';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { Apiv3GridsPaths } from 'core-app/core/apiv3/endpoints/grids/apiv3-grids-paths';
import { Apiv3TimeEntriesPaths } from 'core-app/core/apiv3/endpoints/time-entries/apiv3-time-entries-paths';
import { Apiv3CapabilitiesPaths } from 'core-app/core/apiv3/endpoints/capabilities/apiv3-capabilities-paths';
import { Apiv3MembershipsPaths } from 'core-app/core/apiv3/endpoints/memberships/apiv3-memberships-paths';
import { Apiv3UsersPaths } from 'core-app/core/apiv3/endpoints/users/apiv3-users-paths';
import { APIv3TypesPaths } from 'core-app/core/apiv3/endpoints/types/apiv3-types-paths';
import { APIv3QueriesPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-queries-paths';
import { APIV3WorkPackagesPaths } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths';
import { APIv3ProjectPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-project-paths';
import { APIv3ProjectsPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-projects-paths';
import { APIv3StatusesPaths } from 'core-app/core/apiv3/endpoints/statuses/apiv3-statuses-paths';
import { APIv3RolesPaths } from 'core-app/core/apiv3/endpoints/roles/apiv3-roles-paths';
import { APIv3VersionsPaths } from 'core-app/core/apiv3/endpoints/versions/apiv3-versions-paths';
import { Apiv3RelationsPaths } from 'core-app/core/apiv3/endpoints/relations/apiv3-relations-paths';
import { Apiv3NewsPaths } from 'core-app/core/apiv3/endpoints/news/apiv3-news-paths';
import { Apiv3HelpTextsPaths } from 'core-app/core/apiv3/endpoints/help_texts/apiv3-help-texts-paths';
import { Apiv3ConfigurationPath } from 'core-app/core/apiv3/endpoints/configuration/apiv3-configuration-path';
import { Apiv3BoardsPaths } from 'core-app/core/apiv3/virtual/apiv3-boards-paths';
import { RootResource } from 'core-app/features/hal/resources/root-resource';
import { Apiv3PlaceholderUsersPaths } from 'core-app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-users-paths';
import { Apiv3GroupsPaths } from 'core-app/core/apiv3/endpoints/groups/apiv3-groups-paths';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { Apiv3NotificationsPaths } from 'core-app/core/apiv3/endpoints/notifications/apiv3-notifications-paths';
@Injectable({ providedIn: 'root' })
export class APIV3Service {
@ -144,7 +138,7 @@ export class APIV3Service {
public readonly boards = this.apiV3CustomEndpoint(Apiv3BoardsPaths);
constructor(readonly injector:Injector,
readonly pathHelper:PathHelperService) {
readonly pathHelper:PathHelperService) {
}
/**
@ -159,13 +153,12 @@ export class APIV3Service {
public withOptionalProject(projectIdentifier:string|number|null|undefined):APIv3ProjectPaths|this {
if (_.isNil(projectIdentifier)) {
return this;
} else {
return this.projects.id(projectIdentifier);
}
return this.projects.id(projectIdentifier);
}
public collectionFromString(fullPath:string) {
const path = fullPath.replace(this.pathHelper.api.v3.apiV3Base + '/', '');
const path = fullPath.replace(`${this.pathHelper.api.v3.apiV3Base}/`, '');
return this.apiV3CollectionEndpoint(path);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,19 +26,19 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { States } from "core-app/core/states/states.service";
import { HasId, StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { tap } from "rxjs/operators";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { States } from 'core-app/core/states/states.service';
import { HasId, StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { tap } from 'rxjs/operators';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
export abstract class CachableAPIV3Collection<
T extends HasId = HalResource,
V extends APIv3GettableResource<T> = APIv3GettableResource<T>,
X extends StateCacheService<T> = StateCacheService<T>
X extends StateCacheService<T> = StateCacheService<T>,
>
extends APIv3ResourceCollection<T, V> {
@InjectField() states:States;
@ -51,23 +51,22 @@ export abstract class CachableAPIV3Collection<
public observeAll():Observable<T[]> {
return this.cache.observeAll();
}
/**
* Inserts a collection or single response to cache as an rxjs tap function
*/
protected cacheResponse<R>():(source:Observable<R>) => Observable<R> {
return (source$) => {
return source$.pipe(
tap(
(response:R) => {
if (response instanceof CollectionResource) {
response.elements.forEach(this.touch.bind(this));
} else if (response instanceof HalResource) {
this.touch(response as any);
}
return (source$) => source$.pipe(
tap(
(response:R) => {
if (response instanceof CollectionResource) {
response.elements.forEach(this.touch.bind(this));
} else if (response instanceof HalResource) {
this.touch(response as any);
}
)
);
};
},
),
);
}
/**

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,18 +26,21 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { States } from "core-app/core/states/states.service";
import { HasId, StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { concat, from, merge, Observable, of } from "rxjs";
import { mapTo, publish, share, shareReplay, switchMap, take, tap } from "rxjs/operators";
import { SchemaCacheService } from "core-app/core/schemas/schema-cache.service";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { States } from 'core-app/core/states/states.service';
import { HasId, StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { concat, Observable, of } from 'rxjs';
import {
mapTo, shareReplay, switchMap, take, tap,
} from 'rxjs/operators';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
extends APIv3GettableResource<T> {
@InjectField() states:States;
@InjectField() schemaCache:SchemaCacheService;
readonly cache = this.createCache();
@ -59,12 +62,12 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
.load()
.pipe(
take(1),
shareReplay(1)
shareReplay(1),
);
this.cache.clearAndLoad(
id,
observable
observable,
);
// Return concat of the loading observable
@ -72,14 +75,13 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
// but then continue with the streamed cache
return concat<T>(
observable,
this.cache.state(id).values$()
this.cache.state(id).values$(),
);
}
return this.cache.state(id).values$();
}
/**
* Observe the values of this resource,
* but do not request it actively.
@ -90,7 +92,6 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
.observe(this.id.toString());
}
/**
* Returns a (potentially cached) observable.
*
@ -102,7 +103,7 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
return this
.requireAndStream(false)
.pipe(
take(1)
take(1),
);
}
@ -141,10 +142,9 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
take(1),
mapTo(resource),
);
} else {
return of(resource);
}
})
return of(resource);
}),
) as any; // T does not extend HalResource for virtual endpoints such as board, thus we need to cast here
}
@ -159,13 +159,11 @@ export abstract class CachableAPIV3Resource<T extends HasId = HalResource>
* Inserts a collection response to cache as an rxjs tap function
*/
protected cacheResponse():(source:Observable<T>) => Observable<T> {
return (source$:Observable<T>) => {
return source$.pipe(
tap(
(resource:T) => this.touch(resource)
)
);
};
return (source$:Observable<T>) => source$.pipe(
tap(
(resource:T) => this.touch(resource),
),
);
}
/**

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,8 +27,10 @@
//++
import { MultiInputState, State } from 'reactivestates';
import { Observable } from "rxjs";
import { auditTime, map, share, startWith, take } from "rxjs/operators";
import { Observable } from 'rxjs';
import {
auditTime, map, share, startWith, take,
} from 'rxjs/operators';
export interface HasId {
id:string|null;
@ -36,6 +38,7 @@ export interface HasId {
export class StateCacheService<T> {
protected cacheDurationInMs:number;
protected multiState:MultiInputState<T>;
constructor(state:MultiInputState<T>, holdValuesForSeconds = 3600) {
@ -66,12 +69,11 @@ export class StateCacheService<T> {
* Sets a promise to the state
*/
public clearAndLoad(id:string, loader:Observable<T>):Observable<T> {
const observable =
loader
.pipe(
take(1),
share()
);
const observable = loader
.pipe(
take(1),
share(),
);
this
.multiState.get(id)
@ -102,7 +104,6 @@ export class StateCacheService<T> {
return this.updateValue(resource.id!, resource as any);
}
/**
* Observe the value of the given id
*/
@ -135,7 +136,7 @@ export class StateCacheService<T> {
});
return mapped;
})
}),
);
}
@ -144,7 +145,7 @@ export class StateCacheService<T> {
* @param ids
*/
public clearSome(...ids:string[]) {
ids.forEach(id => this.multiState.get(id).clear());
ids.forEach((id) => this.multiState.get(id).clear());
}
/**
@ -173,4 +174,3 @@ export class StateCacheService<T> {
this.multiState.get(id).putValue(val);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,27 +26,25 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Apiv3CapabilityPaths } from "core-app/core/apiv3/endpoints/capabilities/apiv3-capability-paths";
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { MultiInputState } from "reactivestates";
import { Apiv3CapabilityPaths } from 'core-app/core/apiv3/endpoints/capabilities/apiv3-capability-paths';
import { CapabilityResource } from 'core-app/features/hal/resources/capability-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { CapabilityCacheService } from "core-app/core/apiv3/endpoints/capabilities/capability-cache.service";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CapabilityCacheService } from 'core-app/core/apiv3/endpoints/capabilities/capability-cache.service';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class Apiv3CapabilitiesPaths
extends CachableAPIV3Collection<CapabilityResource, Apiv3CapabilityPaths>
implements Apiv3ListResourceInterface<CapabilityResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'capabilities', Apiv3CapabilityPaths);
}
@ -59,7 +57,7 @@ export class Apiv3CapabilitiesPaths
.halResourceService
.get<CollectionResource<CapabilityResource>>(this.path + listParamsString(params))
.pipe(
this.cacheResponse()
this.cacheResponse(),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,10 +26,10 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { Apiv3CapabilitiesPaths } from "core-app/core/apiv3/endpoints/capabilities/apiv3-capabilities-paths";
import { CapabilityResource } from 'core-app/features/hal/resources/capability-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { Apiv3CapabilitiesPaths } from 'core-app/core/apiv3/endpoints/capabilities/apiv3-capabilities-paths';
export class Apiv3CapabilityPaths extends CachableAPIV3Resource<CapabilityResource> {
protected createCache():StateCacheService<CapabilityResource> {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { SchemaCacheService } from "core-app/core/schemas/schema-cache.service";
import { States } from "core-app/core/states/states.service";
import { Injector } from "@angular/core";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { MultiInputState } from "reactivestates";
import { CapabilityResource } from 'core-app/features/hal/resources/capability-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { States } from 'core-app/core/states/states.service';
import { Injector } from '@angular/core';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { MultiInputState } from 'reactivestates';
export class CapabilityCacheService extends StateCacheService<CapabilityResource> {
@InjectField() readonly states:States;

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,24 +26,20 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { GridResource } from "core-app/features/hal/resources/grid-resource";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { ConfigurationResource } from "core-app/features/hal/resources/configuration-resource";
import { Observable } from "rxjs";
import { shareReplay } from "rxjs/operators";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { ConfigurationResource } from 'core-app/features/hal/resources/configuration-resource';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
export class Apiv3ConfigurationPath extends APIv3GettableResource<ConfigurationResource> {
private $configuration:Observable<ConfigurationResource>;
constructor(protected apiRoot:APIV3Service,
readonly basePath:string) {
readonly basePath:string) {
super(apiRoot, basePath, 'configuration');
}
public get():Observable<ConfigurationResource> {
if (this.$configuration) {
return this.$configuration;
@ -52,7 +48,7 @@ export class Apiv3ConfigurationPath extends APIv3GettableResource<ConfigurationR
return this.$configuration = this.halResourceService
.get<ConfigurationResource>(this.path)
.pipe(
shareReplay()
shareReplay(),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,14 +26,13 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { HalPayloadHelper } from "core-app/features/hal/schemas/hal-payload.helper";
import { GridWidgetResource } from "core-app/features/hal/resources/grid-widget-resource";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
import { GridWidgetResource } from 'core-app/features/hal/resources/grid-widget-resource';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
export class Apiv3GridForm extends APIv3FormResource {
/**
* We need to override the grid widget extraction
* to pass the correct payload to the API.
@ -43,23 +42,21 @@ export class Apiv3GridForm extends APIv3FormResource {
*/
public static extractPayload(resource:HalResource|Object, schema:SchemaResource|null = null):Object {
if (resource instanceof HalResource && schema) {
const grid = resource as HalResource;
const grid = resource;
const payload = HalPayloadHelper.extractPayloadFromSchema(grid, schema);
// The widget only states the type of the widget resource but does not explain
// the widget itself. We therefore have to do that by hand.
if (payload.widgets) {
payload.widgets = grid.widgets.map((widget:GridWidgetResource) => {
return {
id: widget.id,
startRow: widget.startRow,
endRow: widget.endRow,
startColumn: widget.startColumn,
endColumn: widget.endColumn,
identifier: widget.identifier,
options: widget.options
};
});
payload.widgets = grid.widgets.map((widget:GridWidgetResource) => ({
id: widget.id,
startRow: widget.startRow,
endRow: widget.endRow,
startColumn: widget.startColumn,
endColumn: widget.endColumn,
identifier: widget.identifier,
options: widget.options,
}));
}
return payload;
@ -77,5 +74,4 @@ export class Apiv3GridForm extends APIv3FormResource {
public extractPayload(request:HalResource|Object, schema:SchemaResource|null = null) {
return Apiv3GridForm.extractPayload(request, schema);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,11 +26,11 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { GridResource } from "core-app/features/hal/resources/grid-resource";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { Observable } from "rxjs";
import { Apiv3GridForm } from "core-app/core/apiv3/endpoints/grids/apiv3-grid-form";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { GridResource } from 'core-app/features/hal/resources/grid-resource';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { Observable } from 'rxjs';
import { Apiv3GridForm } from 'core-app/core/apiv3/endpoints/grids/apiv3-grid-form';
export class Apiv3GridPaths extends APIv3GettableResource<GridResource> {
// Static paths

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,25 +26,25 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { Apiv3GridPaths } from "core-app/core/apiv3/endpoints/grids/apiv3-grid-paths";
import { GridResource } from "core-app/features/hal/resources/grid-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { Apiv3GridForm } from "core-app/core/apiv3/endpoints/grids/apiv3-grid-form";
import { Observable } from "rxjs";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Apiv3GridPaths } from 'core-app/core/apiv3/endpoints/grids/apiv3-grid-paths';
import { GridResource } from 'core-app/features/hal/resources/grid-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { Apiv3GridForm } from 'core-app/core/apiv3/endpoints/grids/apiv3-grid-form';
import { Observable } from 'rxjs';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
export class Apiv3GridsPaths
extends APIv3ResourceCollection<GridResource, Apiv3GridPaths>
implements Apiv3ListResourceInterface<GridResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'grids', Apiv3GridPaths);
}
@ -70,8 +70,8 @@ export class Apiv3GridsPaths
return this
.halResourceService
.post<GridResource>(
this.path,
this.form.extractPayload(resource, schema)
);
this.path,
this.form.extractPayload(resource, schema),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,9 +26,9 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { GroupResource } from "core-app/features/hal/resources/group-resource";
import { Observable } from "rxjs";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { GroupResource } from 'core-app/features/hal/resources/group-resource';
import { Observable } from 'rxjs';
export class Apiv3GroupPaths extends APIv3GettableResource<GroupResource> {
/**
@ -39,8 +39,8 @@ export class Apiv3GroupPaths extends APIv3GettableResource<GroupResource> {
return this
.halResourceService
.patch<GroupResource>(this.path, {
name: resource.name,
});
name: resource.name,
});
}
/**

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,23 +26,23 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { Apiv3GroupPaths } from "core-app/core/apiv3/endpoints/groups/apiv3-group-paths";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Apiv3GroupPaths } from 'core-app/core/apiv3/endpoints/groups/apiv3-group-paths';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { GroupResource } from "core-app/features/hal/resources/group-resource";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { GroupResource } from 'core-app/features/hal/resources/group-resource';
export class Apiv3GroupsPaths
extends APIv3ResourceCollection<GroupResource, Apiv3GroupPaths>
implements Apiv3ListResourceInterface<GroupResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'groups', Apiv3GroupPaths);
}
@ -65,8 +65,8 @@ export class Apiv3GroupsPaths
return this
.halResourceService
.post<GroupResource>(
this.path,
resource,
);
this.path,
resource,
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { HelpTextResource } from "core-app/features/hal/resources/help-text-resource";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import { HelpTextResource } from 'core-app/features/hal/resources/help-text-resource';
export class Apiv3HelpTextsPaths
extends APIv3ResourceCollection<HelpTextResource, APIv3GettableResource<HelpTextResource>> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'help_texts');
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,14 +26,10 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import {APIv3FormResource} from "core-app/core/apiv3/forms/apiv3-form-resource";
import {SchemaResource} from "core-app/features/hal/resources/schema-resource";
import {HalPayloadHelper} from "core-app/features/hal/schemas/hal-payload.helper";
import {HalResource} from "core-app/features/hal/resources/hal-resource";
import {MembershipResource, MembershipResourceEmbedded} from "core-app/features/hal/resources/membership-resource";
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { MembershipResourceEmbedded } from 'core-app/features/hal/resources/membership-resource';
export class Apiv3MembershipsForm extends APIv3FormResource {
/**
* We need to override the grid widget extraction
* to pass the correct payload to the API.
@ -46,14 +42,14 @@ export class Apiv3MembershipsForm extends APIv3FormResource {
_links: {
project: { href: resource.project.href },
principal: { href: resource.principal.href },
roles: resource.roles.map(role => ({ href: role.href })),
roles: resource.roles.map((role) => ({ href: role.href })),
},
_meta: {
notificationMessage: {
raw: resource.notificationMessage.raw
}
}
}
raw: resource.notificationMessage.raw,
},
},
};
}
/**
@ -65,5 +61,4 @@ export class Apiv3MembershipsForm extends APIv3FormResource {
public extractPayload(request:MembershipResourceEmbedded) {
return Apiv3MembershipsForm.extractPayload(request);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,28 +26,27 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import {APIv3GettableResource, APIv3ResourceCollection} from "core-app/core/apiv3/paths/apiv3-resource";
import {APIV3Service} from "core-app/core/apiv3/api-v3.service";
import {Apiv3AvailableProjectsPaths} from "core-app/core/apiv3/endpoints/projects/apiv3-available-projects-paths";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Apiv3AvailableProjectsPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-available-projects-paths';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface, listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import {Observable} from "rxjs";
import {Apiv3MembershipsForm} from "core-app/core/apiv3/endpoints/memberships/apiv3-memberships-form";
import { MembershipResource, MembershipResourceEmbedded } from "core-app/features/hal/resources/membership-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
Apiv3ListResourceInterface,
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { Observable } from 'rxjs';
import { Apiv3MembershipsForm } from 'core-app/core/apiv3/endpoints/memberships/apiv3-memberships-form';
import { MembershipResource, MembershipResourceEmbedded } from 'core-app/features/hal/resources/membership-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
export class Apiv3MembershipsPaths
extends APIv3ResourceCollection<MembershipResource, APIv3GettableResource<MembershipResource>>
implements Apiv3ListResourceInterface<MembershipResource> {
// Static paths
readonly form = this.subResource('form', Apiv3MembershipsForm);
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'memberships');
}
@ -61,7 +60,6 @@ export class Apiv3MembershipsPaths
.get<CollectionResource<MembershipResource>>(this.path + listParamsString(params));
}
// /api/v3/memberships/available_projects
readonly available_projects = this.subResource('available_projects', Apiv3AvailableProjectsPaths);
@ -75,9 +73,8 @@ export class Apiv3MembershipsPaths
return this
.halResourceService
.post<MembershipResource>(
this.path,
payload,
);
this.path,
payload,
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,24 +26,23 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { TimeEntryResource } from "core-app/features/hal/resources/time-entry-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { TimeEntryResource } from 'core-app/features/hal/resources/time-entry-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { NewsResource } from "core-app/features/hal/resources/news-resource";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { NewsResource } from 'core-app/features/hal/resources/news-resource';
export class Apiv3NewsPaths
extends APIv3ResourceCollection<NewsResource, APIv3GettableResource<NewsResource>>
implements Apiv3ListResourceInterface<NewsResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'news');
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,11 +26,11 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { Observable } from "rxjs";
import { InAppNotification } from "core-app/features/in-app-notifications/store/in-app-notification.model";
import { HttpClient } from "@angular/common/http";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Observable } from 'rxjs';
import { InAppNotification } from 'core-app/features/in-app-notifications/store/in-app-notification.model';
import { HttpClient } from '@angular/common/http';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
export class Apiv3NotificationPaths extends APIv3GettableResource<InAppNotification> {
@InjectField() http:HttpClient;
@ -39,12 +39,12 @@ export class Apiv3NotificationPaths extends APIv3GettableResource<InAppNotificat
return this
.http
.post(
this.path + '/read_ian',
`${this.path}/read_ian`,
{},
{
withCredentials: true,
responseType: 'json'
}
responseType: 'json',
},
);
}
@ -52,12 +52,12 @@ export class Apiv3NotificationPaths extends APIv3GettableResource<InAppNotificat
return this
.http
.post(
this.path + '/unread_ian',
`${this.path}/unread_ian`,
{},
{
withCredentials: true,
responseType: 'json'
}
responseType: 'json',
},
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,32 +26,31 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { Apiv3ListParameters, listParamsString } from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { InAppNotification } from "core-app/features/in-app-notifications/store/in-app-notification.model";
import { Apiv3NotificationPaths } from "core-app/core/apiv3/endpoints/notifications/apiv3-notification-paths";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { HttpClient } from "@angular/common/http";
import { IHALCollection } from "core-app/core/apiv3/types/hal-collection.type";
import { ID } from "@datorama/akita";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import { Apiv3ListParameters, listParamsString } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { InAppNotification } from 'core-app/features/in-app-notifications/store/in-app-notification.model';
import { Apiv3NotificationPaths } from 'core-app/core/apiv3/endpoints/notifications/apiv3-notification-paths';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { HttpClient } from '@angular/common/http';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { ID } from '@datorama/akita';
export class Apiv3NotificationsPaths
extends APIv3ResourceCollection<InAppNotification, Apiv3NotificationPaths> {
@InjectField() http:HttpClient;
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'notifications', Apiv3NotificationPaths);
}
public facet(facet:string, params?:Apiv3ListParameters):Observable<IHALCollection<InAppNotification>> {
if(facet === 'unread') {
if (facet === 'unread') {
return this.unread(params);
} else {
return this.list(params);
};
}
return this.list(params);
}
/**
@ -70,7 +69,7 @@ export class Apiv3NotificationsPaths
public unread(additional?:Apiv3ListParameters):Observable<IHALCollection<InAppNotification>> {
const params:Apiv3ListParameters = {
...additional,
filters: [["readIAN", "=", false]]
filters: [['readIAN', '=', false]],
};
return this.list(params);
@ -84,12 +83,12 @@ export class Apiv3NotificationsPaths
return this
.http
.post(
this.path + '/read_ian' + listParamsString({ filters: [['id', "=", ids.map(id => id.toString())]] }),
`${this.path}/read_ian${listParamsString({ filters: [['id', '=', ids.map((id) => id.toString())]] })}`,
{},
{
withCredentials: true,
responseType: 'json'
}
responseType: 'json',
},
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,9 +26,9 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { PlaceholderUserResource } from "core-app/features/hal/resources/placeholder-user-resource";
import { Observable } from "rxjs";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { PlaceholderUserResource } from 'core-app/features/hal/resources/placeholder-user-resource';
import { Observable } from 'rxjs';
export class Apiv3PlaceholderUserPaths extends APIv3GettableResource<PlaceholderUserResource> {
/**
@ -39,8 +39,8 @@ export class Apiv3PlaceholderUserPaths extends APIv3GettableResource<Placeholder
return this
.halResourceService
.patch<PlaceholderUserResource>(this.path, {
name: resource.name
});
name: resource.name,
});
}
/**

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,23 +26,23 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { Apiv3PlaceholderUserPaths } from "core-app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-user-paths";
import { PlaceholderUserResource } from "core-app/features/hal/resources/placeholder-user-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Apiv3PlaceholderUserPaths } from 'core-app/core/apiv3/endpoints/placeholder-users/apiv3-placeholder-user-paths';
import { PlaceholderUserResource } from 'core-app/features/hal/resources/placeholder-user-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
export class Apiv3PlaceholderUsersPaths
extends APIv3ResourceCollection<PlaceholderUserResource, Apiv3PlaceholderUserPaths>
implements Apiv3ListResourceInterface<PlaceholderUserResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'placeholder_users', Apiv3PlaceholderUserPaths);
}
@ -65,8 +65,8 @@ export class Apiv3PlaceholderUsersPaths
return this
.halResourceService
.post<PlaceholderUserResource>(
this.path,
resource,
);
this.path,
resource,
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,21 +26,21 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { ProjectResource } from "core-app/features/hal/resources/project-resource";
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface, listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { buildApiV3Filter } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
Apiv3ListResourceInterface,
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { buildApiV3Filter } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
export class Apiv3AvailableProjectsPaths
extends APIv3GettableResource<CollectionResource<ProjectResource>>
implements Apiv3ListResourceInterface<ProjectResource> {
/**
* Load a list of available projects with a given list parameter filter
* @param params
@ -64,12 +64,11 @@ export class Apiv3AvailableProjectsPaths
return this
.halResourceService
.get<CollectionResource<ProjectResource>>(
this.path,
{ filters: buildApiV3Filter('id', '=', [projectId]).toJson() }
)
this.path,
{ filters: buildApiV3Filter('id', '=', [projectId]).toJson() },
)
.pipe(
map(collection => collection.count > 0)
map((collection) => collection.count > 0),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,13 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { SimpleResource } from "core-app/core/apiv3/paths/path-resources";
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { SimpleResource } from 'core-app/core/apiv3/paths/path-resources';
export class APIv3ProjectCopyPaths extends SimpleResource {
constructor(protected apiRoot:APIV3Service,
public basePath:string) {
public basePath:string) {
super(basePath, 'copy');
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,15 +26,15 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3QueriesPaths } from "core-app/core/apiv3/endpoints/queries/apiv3-queries-paths";
import { APIv3TypesPaths } from "core-app/core/apiv3/endpoints/types/apiv3-types-paths";
import { APIV3WorkPackagesPaths } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths";
import { ProjectResource } from "core-app/features/hal/resources/project-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { APIv3VersionsPaths } from "core-app/core/apiv3/endpoints/versions/apiv3-versions-paths";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { APIv3ProjectsPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-projects-paths";
import { APIv3ProjectCopyPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-project-copy-paths";
import { APIv3QueriesPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-queries-paths';
import { APIv3TypesPaths } from 'core-app/core/apiv3/endpoints/types/apiv3-types-paths';
import { APIV3WorkPackagesPaths } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { APIv3VersionsPaths } from 'core-app/core/apiv3/endpoints/versions/apiv3-versions-paths';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { APIv3ProjectsPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-projects-paths';
import { APIv3ProjectCopyPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-project-copy-paths';
export class APIv3ProjectPaths extends CachableAPIV3Resource<ProjectResource> {
// /api/v3/projects/:project_id/available_assignees

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,26 +26,26 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ProjectPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-project-paths";
import { ProjectResource } from "core-app/features/hal/resources/project-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { APIv3ProjectPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-project-paths';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { Observable } from "rxjs";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { ProjectCache } from "core-app/core/apiv3/endpoints/projects/project.cache";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { Observable } from 'rxjs';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { ProjectCache } from 'core-app/core/apiv3/endpoints/projects/project.cache';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
export class APIv3ProjectsPaths
extends CachableAPIV3Collection<ProjectResource, APIv3ProjectPaths>
implements Apiv3ListResourceInterface<ProjectResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'projects', APIv3ProjectPaths);
}
@ -62,7 +62,7 @@ export class APIv3ProjectsPaths
.halResourceService
.get<CollectionResource<ProjectResource>>(this.path + listParamsString(params))
.pipe(
this.cacheResponse()
this.cacheResponse(),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,13 +27,11 @@
//++
import { MultiInputState } from 'reactivestates';
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { Injectable, Injector } from '@angular/core';
import { debugLog } from "core-app/shared/helpers/debug_output";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { SchemaCacheService } from "core-app/core/schemas/schema-cache.service";
import { ProjectResource } from "core-app/features/hal/resources/project-resource";
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
@Injectable()
export class ProjectCache extends StateCacheService<ProjectResource> {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,24 +26,24 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { APIv3QueryPaths } from "core-app/core/apiv3/endpoints/queries/apiv3-query-paths";
import { QueryResource } from "core-app/features/hal/resources/query-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Apiv3QueryForm } from "core-app/core/apiv3/endpoints/queries/apiv3-query-form";
import { Observable } from "rxjs";
import { QueryFormResource } from "core-app/features/hal/resources/query-form-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Apiv3ListParameters, listParamsString } from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { QueryFiltersService } from "core-app/features/work-packages/components/wp-query/query-filters.service";
import { HalPayloadHelper } from "core-app/features/hal/schemas/hal-payload.helper";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { APIv3QueryPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-paths';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Apiv3QueryForm } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-form';
import { Observable } from 'rxjs';
import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { Apiv3ListParameters, listParamsString } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { QueryFiltersService } from 'core-app/features/work-packages/components/wp-query/query-filters.service';
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, APIv3QueryPaths> {
@InjectField() private queryFilters:QueryFiltersService;
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'queries', APIv3QueryPaths);
}
@ -88,7 +88,6 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
.get<QueryResource>(path, queryData);
}
/**
* Stream the response for the given query request
*
@ -97,9 +96,9 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
public parameterised(params:Object):Observable<QueryResource> {
return this.halResourceService
.get<QueryResource>(
this.default.path,
params
);
this.default.path,
params,
);
}
/**
@ -118,8 +117,8 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
return this
.halResourceService
.post<QueryResource>(
this.apiRoot.queries.path, payload
);
this.apiRoot.queries.path, payload,
);
}
/**
@ -130,9 +129,8 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
public toggleStarred(query:QueryResource):Promise<unknown> {
if (query.starred) {
return query.unstar();
} else {
return query.star();
}
return query.star();
}
/**
@ -142,7 +140,7 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
*/
public filterNonHidden(projectIdentifier?:string|null):Observable<CollectionResource<QueryResource>> {
const listParams:Apiv3ListParameters = {
filters: [['hidden', '=', ['f']]]
filters: [['hidden', '=', ['f']]],
};
if (projectIdentifier) {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,14 +26,14 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { QueryResource } from "core-app/features/hal/resources/query-resource";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { QueryFormResource } from "core-app/features/hal/resources/query-form-resource";
import { Observable } from "rxjs";
import * as URI from "urijs";
import { map, tap } from "rxjs/operators";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { QueryFiltersService } from "core-app/features/work-packages/components/wp-query/query-filters.service";
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource';
import { Observable } from 'rxjs';
import * as URI from 'urijs';
import { map, tap } from 'rxjs/operators';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { QueryFiltersService } from 'core-app/features/work-packages/components/wp-query/query-filters.service';
export class Apiv3QueryForm extends APIv3FormResource<QueryFormResource> {
@InjectField() private queryFilters:QueryFiltersService;
@ -47,23 +47,23 @@ export class Apiv3QueryForm extends APIv3FormResource<QueryFormResource> {
// can check whether form saving is possible.
// The query needs a name to be valid.
const payload:any = {
'name': query.name || '!!!__O__o__O__!!!'
name: query.name || '!!!__O__o__O__!!!',
};
if (query.project) {
payload['_links'] = {
'project': {
'href': query.project.href
}
payload._links = {
project: {
href: query.project.href,
},
};
}
const path = this.apiRoot.queries.withOptionalId(query.id).form.path;
const { path } = this.apiRoot.queries.withOptionalId(query.id).form;
return this.halResourceService
.post<QueryFormResource>(path, payload)
.pipe(
tap(form => this.queryFilters.setSchemas(form.$embedded.schema.$embedded.filtersSchemas)),
map(form => [form, this.buildQueryResource(form)])
tap((form) => this.queryFilters.setSchemas(form.$embedded.schema.$embedded.filtersSchemas)),
map((form) => [form, this.buildQueryResource(form)]),
);
}
@ -75,7 +75,7 @@ export class Apiv3QueryForm extends APIv3FormResource<QueryFormResource> {
* @param projectIdentifier
* @param payload
*/
public loadWithParams(params:{[key:string]:unknown}, queryId:string|undefined, projectIdentifier:string|undefined|null, payload:any = {}):Observable<[QueryFormResource, QueryResource]> {
public loadWithParams(params:{ [key:string]:unknown }, queryId:string|undefined, projectIdentifier:string|undefined|null, payload:any = {}):Observable<[QueryFormResource, QueryResource]> {
// We need a valid payload so that we
// can check whether form saving is possible.
// The query needs a name to be valid.
@ -86,18 +86,17 @@ export class Apiv3QueryForm extends APIv3FormResource<QueryFormResource> {
if (projectIdentifier) {
payload._links = payload._links || {};
payload._links.project = {
'href': this.apiRoot.projects.id(projectIdentifier).toString()
href: this.apiRoot.projects.id(projectIdentifier).toString(),
};
}
const path = this.apiRoot.queries.withOptionalId(queryId).form.path;
const { path } = this.apiRoot.queries.withOptionalId(queryId).form;
const href = URI(path).search(params).toString();
return this.halResourceService
.post<QueryFormResource>(href, payload)
.pipe(
tap(form => this.queryFilters.setSchemas(form.$embedded.schema.$embedded.filtersSchemas)),
map(form => [form, this.buildQueryResource(form)])
tap((form) => this.queryFilters.setSchemas(form.$embedded.schema.$embedded.filtersSchemas)),
map((form) => [form, this.buildQueryResource(form)]),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,10 +26,10 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injector } from "@angular/core";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { HttpClient } from "@angular/common/http";
import { SimpleResource } from "core-app/core/apiv3/paths/path-resources";
import { Injector } from '@angular/core';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { HttpClient } from '@angular/common/http';
import { SimpleResource } from 'core-app/core/apiv3/paths/path-resources';
export type QueryOrder = { [wpId:string]:number };
@ -37,34 +37,34 @@ export class APIV3QueryOrder extends SimpleResource {
@InjectField() http:HttpClient;
constructor(readonly injector:Injector,
readonly basePath:string,
readonly id:string|number) {
readonly basePath:string,
readonly id:string|number) {
super(basePath, id);
}
public get():Promise<QueryOrder> {
return this.http
.get<QueryOrder>(
this.path
)
this.path,
)
.toPromise()
.then(result => result || {});
.then((result) => result || {});
}
public update(delta:QueryOrder):Promise<string> {
return this.http
.patch(
this.path,
{ delta: delta },
{ withCredentials: true }
{ delta },
{ withCredentials: true },
)
.toPromise()
.then((response:{t:string}) => response.t);
.then((response:{ t:string }) => response.t);
}
public delete(id:string, ...wpIds:string[]) {
const delta:QueryOrder = {};
wpIds.forEach(id => delta[id] = -1);
wpIds.forEach((id) => delta[id] = -1);
return this.update(delta);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { QueryResource } from "core-app/features/hal/resources/query-resource";
import { APIV3QueryOrder } from "core-app/core/apiv3/endpoints/queries/apiv3-query-order";
import { Apiv3QueryForm } from "core-app/core/apiv3/endpoints/queries/apiv3-query-form";
import { Observable } from "rxjs";
import { QueryFormResource } from "core-app/features/hal/resources/query-form-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { QueryFiltersService } from "core-app/features/work-packages/components/wp-query/query-filters.service";
import { HalPayloadHelper } from "core-app/features/hal/schemas/hal-payload.helper";
import { PaginationObject } from "core-app/shared/components/table-pagination/pagination-service";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { APIV3QueryOrder } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-order';
import { Apiv3QueryForm } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-form';
import { Observable } from 'rxjs';
import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { QueryFiltersService } from 'core-app/features/work-packages/components/wp-query/query-filters.service';
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
import { PaginationObject } from 'core-app/shared/components/table-pagination/pagination-service';
export class APIv3QueryPaths extends APIv3GettableResource<QueryResource> {
@InjectField() private queryFilters:QueryFiltersService;
@ -88,5 +88,4 @@ export class APIv3QueryPaths extends APIv3GettableResource<QueryResource> {
public paginated(pagination:PaginationObject):Observable<QueryResource> {
return this.parameterised(pagination);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,17 +26,17 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { from, Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { RelationResource } from "core-app/features/hal/resources/relation-resource";
import { map } from "rxjs/operators";
import { buildApiV3Filter } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
import { APIv3GettableResource, APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { from, Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { RelationResource } from 'core-app/features/hal/resources/relation-resource';
import { map } from 'rxjs/operators';
import { buildApiV3Filter } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
export class Apiv3RelationsPaths extends APIv3ResourceCollection<RelationResource, APIv3GettableResource<RelationResource>> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'relations');
}
@ -50,7 +50,7 @@ export class Apiv3RelationsPaths extends APIv3ResourceCollection<RelationResourc
}
public loadInvolved(workPackageIds:string[]):Observable<RelationResource[]> {
const validIds = _.filter(workPackageIds, id => /\d+/.test(id));
const validIds = _.filter(workPackageIds, (id) => /\d+/.test(id));
if (validIds.length === 0) {
return from([]);
@ -60,7 +60,7 @@ export class Apiv3RelationsPaths extends APIv3ResourceCollection<RelationResourc
.filtered(buildApiV3Filter('involved', '=', validIds))
.get()
.pipe(
map(collection => collection.elements)
map((collection) => collection.elements),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,12 +26,11 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { RoleResource } from "core-app/features/hal/resources/role-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { RoleResource } from 'core-app/features/hal/resources/role-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class APIv3RolePaths extends CachableAPIV3Resource<RoleResource> {
protected createCache():StateCacheService<RoleResource> {
return new StateCacheService<RoleResource>(this.states.roles);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,18 +26,17 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection, APIv3ResourcePath } from "core-app/core/apiv3/paths/apiv3-resource";
import { Injector } from "@angular/core";
import { RoleResource } from "core-app/features/hal/resources/role-resource";
import { APIv3RolePaths } from "core-app/core/apiv3/endpoints/roles/apiv3-role-paths";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { tap } from "rxjs/operators";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { RoleResource } from 'core-app/features/hal/resources/role-resource';
import { APIv3RolePaths } from 'core-app/core/apiv3/endpoints/roles/apiv3-role-paths';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { tap } from 'rxjs/operators';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
export class APIv3RolesPaths extends APIv3ResourceCollection<RoleResource, APIv3RolePaths> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'roles', APIv3RolePaths);
}
@ -49,12 +48,11 @@ export class APIv3RolesPaths extends APIv3ResourceCollection<RoleResource, APIv3
.halResourceService
.get<CollectionResource<RoleResource>>(this.path)
.pipe(
tap(collection => {
tap((collection) => {
collection.elements.forEach((resource, id) => {
this.id(resource.id!).cache.updateValue(resource.id!, resource);
});
})
}),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,12 +26,11 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { StatusResource } from "core-app/features/hal/resources/status-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { StatusResource } from 'core-app/features/hal/resources/status-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class APIv3StatusPaths extends CachableAPIV3Resource<StatusResource> {
protected createCache():StateCacheService<StatusResource> {
return new StateCacheService<StatusResource>(this.states.statuses);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,18 +26,17 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection, APIv3ResourcePath } from "core-app/core/apiv3/paths/apiv3-resource";
import { Injector } from "@angular/core";
import { StatusResource } from "core-app/features/hal/resources/status-resource";
import { APIv3StatusPaths } from "core-app/core/apiv3/endpoints/statuses/apiv3-status-paths";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { tap } from "rxjs/operators";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { StatusResource } from 'core-app/features/hal/resources/status-resource';
import { APIv3StatusPaths } from 'core-app/core/apiv3/endpoints/statuses/apiv3-status-paths';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { tap } from 'rxjs/operators';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
export class APIv3StatusesPaths extends APIv3ResourceCollection<StatusResource, APIv3StatusPaths> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'statuses', APIv3StatusPaths);
}
@ -49,12 +48,11 @@ export class APIv3StatusesPaths extends APIv3ResourceCollection<StatusResource,
.halResourceService
.get<CollectionResource<StatusResource>>(this.path)
.pipe(
tap(collection => {
tap((collection) => {
collection.elements.forEach((resource, id) => {
this.id(resource.id!).cache.updateValue(resource.id!, resource);
});
})
}),
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,28 +26,26 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Apiv3TimeEntryPaths } from "core-app/core/apiv3/endpoints/time-entries/apiv3-time-entry-paths";
import { TimeEntryResource } from "core-app/features/hal/resources/time-entry-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { Observable } from "rxjs";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { MultiInputState } from "reactivestates";
import { Apiv3TimeEntryPaths } from 'core-app/core/apiv3/endpoints/time-entries/apiv3-time-entry-paths';
import { TimeEntryResource } from 'core-app/features/hal/resources/time-entry-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { Observable } from 'rxjs';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import {
Apiv3ListParameters,
Apiv3ListResourceInterface,
listParamsString
} from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { TimeEntryCacheService } from "core-app/core/apiv3/endpoints/time-entries/time-entry-cache.service";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { TimeEntryCacheService } from 'core-app/core/apiv3/endpoints/time-entries/time-entry-cache.service';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class Apiv3TimeEntriesPaths
extends CachableAPIV3Collection<TimeEntryResource, Apiv3TimeEntryPaths>
implements Apiv3ListResourceInterface<TimeEntryResource> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'time_entries', Apiv3TimeEntryPaths);
}
@ -63,7 +61,7 @@ export class Apiv3TimeEntriesPaths
.halResourceService
.get<CollectionResource<TimeEntryResource>>(this.path + listParamsString(params))
.pipe(
this.cacheResponse()
this.cacheResponse(),
);
}
@ -76,7 +74,7 @@ export class Apiv3TimeEntriesPaths
.halResourceService
.post<TimeEntryResource>(this.path, payload)
.pipe(
this.cacheResponse()
this.cacheResponse(),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { TimeEntryResource } from "core-app/features/hal/resources/time-entry-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import { Apiv3TimeEntriesPaths } from "core-app/core/apiv3/endpoints/time-entries/apiv3-time-entries-paths";
import { HalPayloadHelper } from "core-app/features/hal/schemas/hal-payload.helper";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { TimeEntryResource } from 'core-app/features/hal/resources/time-entry-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Apiv3TimeEntriesPaths } from 'core-app/core/apiv3/endpoints/time-entries/apiv3-time-entries-paths';
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
export class Apiv3TimeEntryPaths extends CachableAPIV3Resource<TimeEntryResource> {
// Static paths
@ -54,7 +54,7 @@ export class Apiv3TimeEntryPaths extends CachableAPIV3Resource<TimeEntryResource
.halResourceService
.patch<TimeEntryResource>(this.path, this.extractPayload(payload, schema))
.pipe(
tap(resource => this.touch(resource))
tap((resource) => this.touch(resource)),
);
}
@ -66,7 +66,7 @@ export class Apiv3TimeEntryPaths extends CachableAPIV3Resource<TimeEntryResource
.halResourceService
.delete<TimeEntryResource>(this.path)
.pipe(
tap(() => this.cache.clearSome(this.id.toString()))
tap(() => this.cache.clearSome(this.id.toString())),
);
}
@ -84,10 +84,9 @@ export class Apiv3TimeEntryPaths extends CachableAPIV3Resource<TimeEntryResource
protected extractPayload(resource:HalResource|Object|null, schema:SchemaResource|null = null) {
if (resource instanceof HalResource && schema) {
return HalPayloadHelper.extractPayloadFromSchema(resource, schema);
} else if (!(resource instanceof HalResource)) {
} if (!(resource instanceof HalResource)) {
return resource;
} else {
return {};
}
return {};
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,17 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { TimeEntryResource } from "core-app/features/hal/resources/time-entry-resource";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { SchemaCacheService } from "core-app/core/schemas/schema-cache.service";
import { States } from "core-app/core/states/states.service";
import { Injector } from "@angular/core";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { MultiInputState } from "reactivestates";
import { TimeEntryResource } from 'core-app/features/hal/resources/time-entry-resource';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { States } from 'core-app/core/states/states.service';
import { Injector } from '@angular/core';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { MultiInputState } from 'reactivestates';
export class TimeEntryCacheService extends StateCacheService<TimeEntryResource> {
@InjectField() readonly states:States;
@InjectField() readonly schemaCache:SchemaCacheService;
constructor(readonly injector:Injector, state:MultiInputState<TimeEntryResource>) {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { TypeResource } from "core-app/features/hal/resources/type-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { APIv3TypesPaths } from "core-app/core/apiv3/endpoints/types/apiv3-types-paths";
import { TypeResource } from 'core-app/features/hal/resources/type-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { APIv3TypesPaths } from 'core-app/core/apiv3/endpoints/types/apiv3-types-paths';
export class APIv3TypePaths extends CachableAPIV3Resource<TypeResource> {
protected createCache():StateCacheService<TypeResource> {
return (this.parent as APIv3TypesPaths).cache;
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,15 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { TypeResource } from "core-app/features/hal/resources/type-resource";
import { APIv3TypePaths } from "core-app/core/apiv3/endpoints/types/apiv3-type-paths";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { TypeResource } from 'core-app/features/hal/resources/type-resource';
import { APIv3TypePaths } from 'core-app/core/apiv3/endpoints/types/apiv3-type-paths';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class APIv3TypesPaths extends CachableAPIV3Collection<TypeResource, APIv3TypePaths> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'types', APIv3TypePaths);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { UserResource } from "core-app/features/hal/resources/user-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { Apiv3UserPreferencesPaths } from "core-app/core/apiv3/endpoints/users/apiv3-user-preferences-paths";
import { UserResource } from 'core-app/features/hal/resources/user-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { Apiv3UserPreferencesPaths } from 'core-app/core/apiv3/endpoints/users/apiv3-user-preferences-paths';
export class APIv3UserPaths extends CachableAPIV3Resource<UserResource> {
readonly avatar = this.subResource('avatar');
readonly preferences = this.subResource('preferences', Apiv3UserPreferencesPaths);

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,15 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourcePath } from "core-app/core/apiv3/paths/apiv3-resource";
import { Observable } from "rxjs";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { HttpClient } from "@angular/common/http";
import { UserPreferencesModel } from "core-app/features/user-preferences/state/user-preferences.model";
import { APIv3ResourcePath } from 'core-app/core/apiv3/paths/apiv3-resource';
import { Observable } from 'rxjs';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { HttpClient } from '@angular/common/http';
import { UserPreferencesModel } from 'core-app/features/user-preferences/state/user-preferences.model';
export class Apiv3UserPreferencesPaths extends APIv3ResourcePath<UserPreferencesModel> {
@InjectField() http:HttpClient;
/**
* Perform a request to the backend to load preferences
*/
@ -43,8 +42,8 @@ export class Apiv3UserPreferencesPaths extends APIv3ResourcePath<UserPreferences
return this
.http
.get<UserPreferencesModel>(
this.path,
);
this.path,
);
}
/**
@ -54,9 +53,9 @@ export class Apiv3UserPreferencesPaths extends APIv3ResourcePath<UserPreferences
return this
.http
.patch<UserPreferencesModel>(
this.path,
payload,
{ withCredentials: true, responseType: 'json' }
);
this.path,
payload,
{ withCredentials: true, responseType: 'json' },
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { APIv3UserPaths } from "core-app/core/apiv3/endpoints/users/apiv3-user-paths";
import { Observable } from "rxjs";
import { UserResource } from "core-app/features/hal/resources/user-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { APIv3UserPaths } from 'core-app/core/apiv3/endpoints/users/apiv3-user-paths';
import { Observable } from 'rxjs';
import { UserResource } from 'core-app/features/hal/resources/user-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
export class Apiv3UsersPaths extends APIv3ResourceCollection<UserResource, APIv3UserPaths> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'users', APIv3UserPaths);
}
@ -68,8 +68,8 @@ export class Apiv3UsersPaths extends APIv3ResourceCollection<UserResource, APIv3
return this
.halResourceService
.post<UserResource>(
this.path,
resource,
);
this.path,
resource,
);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,15 +26,13 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { VersionResource } from "core-app/features/hal/resources/version-resource";
import { Observable } from "rxjs";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { MultiInputState } from "reactivestates";
import { tap } from "rxjs/operators";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { VersionResource } from 'core-app/features/hal/resources/version-resource';
import { Observable } from 'rxjs';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { tap } from 'rxjs/operators';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class APIv3VersionPaths extends CachableAPIV3Resource<VersionResource> {
/**
* Update a version resource with the given payload
*
@ -45,11 +43,11 @@ export class APIv3VersionPaths extends CachableAPIV3Resource<VersionResource> {
return this
.halResourceService
.patch<VersionResource>(
this.path,
payload
)
this.path,
payload,
)
.pipe(
tap(version => this.touch(version))
tap((version) => this.touch(version)),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,19 +26,19 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource, APIv3ResourceCollection } from "core-app/core/apiv3/paths/apiv3-resource";
import { VersionResource } from "core-app/features/hal/resources/version-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { Observable } from "rxjs";
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Apiv3AvailableProjectsPaths } from "core-app/core/apiv3/endpoints/projects/apiv3-available-projects-paths";
import { APIv3VersionPaths } from "core-app/core/apiv3/endpoints/versions/apiv3-version-paths";
import { APIv3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource';
import { VersionResource } from 'core-app/features/hal/resources/version-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { Observable } from 'rxjs';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { Apiv3AvailableProjectsPaths } from 'core-app/core/apiv3/endpoints/projects/apiv3-available-projects-paths';
import { APIv3VersionPaths } from 'core-app/core/apiv3/endpoints/versions/apiv3-version-paths';
export class APIv3VersionsPaths extends APIv3ResourceCollection<VersionResource, APIv3VersionPaths> {
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'versions', APIv3VersionPaths);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { WorkPackageCollectionResource } from "core-app/features/hal/resources/wp-collection-resource";
import { Observable } from "rxjs";
import { APIV3WorkPackagesPaths } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths";
import { take, tap } from "rxjs/operators";
import { WorkPackageCache } from "core-app/core/apiv3/endpoints/work_packages/work-package.cache";
import { States } from "core-app/core/states/states.service";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
import { Observable } from 'rxjs';
import { APIV3WorkPackagesPaths } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths';
import { take, tap } from 'rxjs/operators';
import { WorkPackageCache } from 'core-app/core/apiv3/endpoints/work_packages/work-package.cache';
import { States } from 'core-app/core/states/states.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
export class ApiV3WorkPackageCachedSubresource extends APIv3GettableResource<WorkPackageCollectionResource> {
@InjectField() private states:States;
@ -45,9 +45,9 @@ export class ApiV3WorkPackageCachedSubresource extends APIv3GettableResource<Wor
.halResourceService
.get<WorkPackageCollectionResource>(this.path)
.pipe(
tap(collection => collection.schemas && this.updateSchemas(collection.schemas)),
tap(collection => this.cache.updateWorkPackageList(collection.elements)),
take(1)
tap((collection) => collection.schemas && this.updateSchemas(collection.schemas)),
tap((collection) => this.cache.updateWorkPackageList(collection.elements)),
take(1),
);
}
@ -56,7 +56,7 @@ export class ApiV3WorkPackageCachedSubresource extends APIv3GettableResource<Wor
}
private updateSchemas(schemas:CollectionResource<SchemaResource>) {
schemas.elements.forEach(schema => {
schemas.elements.forEach((schema) => {
this.states.schemas.get(schema.href as string).putValue(schema);
});
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,14 +26,13 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { Apiv3RelationsPaths } from "core-app/core/apiv3/endpoints/relations/apiv3-relations-paths";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { APIV3WorkPackagesPaths } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { Apiv3RelationsPaths } from 'core-app/core/apiv3/endpoints/relations/apiv3-relations-paths';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { APIV3WorkPackagesPaths } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class APIV3WorkPackagePaths extends CachableAPIV3Resource<WorkPackageResource> {
// /api/v3/(?:projectPath)/work_packages/(:workPackageId)/relations
public readonly relations = this.subResource('relations', Apiv3RelationsPaths);

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,26 +26,29 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { APIV3WorkPackagePaths } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-package-paths";
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { WorkPackageCollectionResource } from "core-app/features/hal/resources/wp-collection-resource";
import { Observable } from "rxjs";
import { APIv3WorkPackageForm } from "core-app/core/apiv3/endpoints/work_packages/apiv3-work-package-form";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { WorkPackageCache } from "core-app/core/apiv3/endpoints/work_packages/work-package.cache";
import { APIv3GettableResource } from "core-app/core/apiv3/paths/apiv3-resource";
import { ApiV3WorkPackageCachedSubresource } from "core-app/core/apiv3/endpoints/work_packages/api-v3-work-package-cached-subresource";
import { ApiV3FilterBuilder, buildApiV3Filter } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
import { Observable } from 'rxjs';
import { APIV3WorkPackagePaths } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-package-paths';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
import { APIv3WorkPackageForm } from 'core-app/core/apiv3/endpoints/work_packages/apiv3-work-package-form';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { WorkPackageCache } from 'core-app/core/apiv3/endpoints/work_packages/work-package.cache';
import { APIv3GettableResource } from 'core-app/core/apiv3/paths/apiv3-resource';
import { ApiV3WorkPackageCachedSubresource } from 'core-app/core/apiv3/endpoints/work_packages/api-v3-work-package-cached-subresource';
import {
ApiV3FilterBuilder,
ApiV3FilterValueType,
buildApiV3Filter,
} from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageResource, APIV3WorkPackagePaths, WorkPackageCache> {
// Base path
public readonly path:string;
constructor(readonly apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'work_packages', APIV3WorkPackagePaths);
}
@ -75,7 +78,6 @@ export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageR
if (results.elements) {
this.cache.updateWorkPackageList(results.elements);
}
});
resolve(undefined);
@ -94,7 +96,7 @@ export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageR
.halResourceService
.post<WorkPackageResource>(this.path, payload)
.pipe(
this.cacheResponse()
this.cacheResponse(),
);
}
@ -121,7 +123,7 @@ export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageR
sortBy: '[["updatedAt","desc"]]',
offset: '1',
pageSize: '10',
...additionalParams
...additionalParams,
};
return this.filtered(filters, params);
@ -132,14 +134,14 @@ export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageR
* @param ids work package IDs to filter for
* @param timestamp The timestamp to clip at
*/
public filterUpdatedSince(ids:(string|null)[], timestamp:unknown):ApiV3WorkPackageCachedSubresource {
public filterUpdatedSince(ids:(string|null)[], timestamp:ApiV3FilterValueType):ApiV3WorkPackageCachedSubresource {
const filters = new ApiV3FilterBuilder()
.add('id', '=', ids.filter((n:string|null) => n)) // no null values
.add('id', '=', (ids.filter((n) => n) as string[]))
.add('updatedAt', '<>d', [timestamp, '']);
const params = {
offset: '1',
pageSize: '10'
pageSize: '10',
};
return this.filtered(filters, params);
@ -156,12 +158,12 @@ export class APIV3WorkPackagesPaths extends CachableAPIV3Collection<WorkPackageR
return this
.halResourceService
.getAllPaginated<WorkPackageCollectionResource[]>(
this.path,
ids.length,
{
filters: buildApiV3Filter('id', '=', ids).toJson(),
}
);
this.path,
ids.length,
{
filters: buildApiV3Filter('id', '=', ids).toJson(),
},
);
}
protected createCache():WorkPackageCache {

@ -1,7 +1,7 @@
import { APIv3FormResource } from "core-app/core/apiv3/forms/apiv3-form-resource";
import { FormResource } from "core-app/features/hal/resources/form-resource";
import { Observable } from "rxjs";
import { HalSource } from "core-app/features/hal/resources/hal-resource";
import { APIv3FormResource } from 'core-app/core/apiv3/forms/apiv3-form-resource';
import { FormResource } from 'core-app/features/hal/resources/form-resource';
import { Observable } from 'rxjs';
import { HalSource } from 'core-app/features/hal/resources/hal-resource';
export class APIv3WorkPackageForm extends APIv3FormResource {
/**
@ -12,10 +12,11 @@ export class APIv3WorkPackageForm extends APIv3FormResource {
* @returns A work package form resource prefilled with the provided payload.
*/
public forTypePayload(payload:HalSource):Observable<FormResource> {
const typePayload = payload._links['type'] ? { _links: { type: payload['_links']['type'] } } : { _links: {} } ;
const typePayload = payload._links.type ? { _links: { type: payload._links.type } } : { _links: {} };
return this.post(payload);
}
/**
* Returns a promise to post `/api/v3/work_packages/form` where the
* payload sent to the backend has been provided.
@ -27,4 +28,3 @@ export class APIv3WorkPackageForm extends APIv3FormResource {
return this.post(payload);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -31,20 +31,20 @@ import { TestBed } from '@angular/core/testing';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { NotificationsService } from 'core-app/shared/components/notifications/notifications.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { States } from 'core-app/core/states/states.service';
import { take, takeWhile } from 'rxjs/operators';
import { WorkPackagesActivityService } from "core-app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service";
import { ConfigurationService } from "core-app/core/config/configuration.service";
import { WorkPackageNotificationService } from "core-app/features/work-packages/services/notifications/work-package-notification.service";
import { WorkPackageCache } from "core-app/core/apiv3/endpoints/work_packages/work-package.cache";
import { OpenProjectFileUploadService } from "core-app/core/file-upload/op-file-upload.service";
import { OpenProjectDirectFileUploadService } from "core-app/core/file-upload/op-direct-file-upload.service";
import { TimezoneService } from "core-app/core/datetime/timezone.service";
import { HalResourceNotificationService } from "core-app/features/hal/services/hal-resource-notification.service";
import { OpenprojectHalModule } from "core-app/features/hal/openproject-hal.module";
import { WorkPackagesActivityService } from 'core-app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service';
import { WorkPackageCache } from 'core-app/core/apiv3/endpoints/work_packages/work-package.cache';
import { OpenProjectFileUploadService } from 'core-app/core/file-upload/op-file-upload.service';
import { OpenProjectDirectFileUploadService } from 'core-app/core/file-upload/op-direct-file-upload.service';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { OpenprojectHalModule } from 'core-app/features/hal/openproject-hal.module';
describe('WorkPackageCache', () => {
let injector:Injector;
@ -73,7 +73,7 @@ describe('WorkPackageCache', () => {
{ provide: WorkPackageNotificationService, useValue: {} },
{ provide: OpenProjectFileUploadService, useValue: {} },
{ provide: OpenProjectDirectFileUploadService, useValue: {} },
]
],
});
injector = TestBed.inject(Injector);
@ -84,27 +84,26 @@ describe('WorkPackageCache', () => {
// sinon.stub(schemaCacheService, 'ensureLoaded').returns(Promise.resolve(true));
spyOn(schemaCacheService, 'ensureLoaded').and.returnValue(Promise.resolve(true as any));
const workPackage1 = new WorkPackageResource(
injector,
{
id: '1',
_links: {
self: ''
}
self: '',
},
},
true,
(wp:WorkPackageResource) => undefined,
'WorkPackage'
'WorkPackage',
);
dummyWorkPackages = [workPackage1 as any];
});
it('returns a work package after the list has been initialized', function (done:any) {
it('returns a work package after the list has been initialized', (done:any) => {
workPackageCache.state('1').values$()
.pipe(
take(1)
take(1),
)
.subscribe((wp:WorkPackageResource) => {
expect(wp.id!).toEqual('1');
@ -119,7 +118,7 @@ describe('WorkPackageCache', () => {
workPackageCache.state('1').values$()
.pipe(
takeWhile((wp) => count < 2)
takeWhile((wp) => count < 2),
)
.subscribe((wp:WorkPackageResource) => {
expect(wp.id!).toEqual('1');
@ -134,5 +133,4 @@ describe('WorkPackageCache', () => {
workPackageCache.updateWorkPackageList([dummyWorkPackages[0]], false);
workPackageCache.updateWorkPackageList([dummyWorkPackages[0]], false);
});
});

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,12 +27,12 @@
//++
import { MultiInputState } from 'reactivestates';
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { Injectable, Injector } from '@angular/core';
import { debugLog } from "core-app/shared/helpers/debug_output";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { SchemaCacheService } from "core-app/core/schemas/schema-cache.service";
import { debugLog } from 'core-app/shared/helpers/debug_output';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
@Injectable()
export class WorkPackageCache extends StateCacheService<WorkPackageResource> {
@ -53,13 +53,12 @@ export class WorkPackageCache extends StateCacheService<WorkPackageResource> {
updateWorkPackage(wp:WorkPackageResource, immediate = false):Promise<WorkPackageResource> {
if (immediate || wp.isNew) {
return super.updateValue(wp.id!, wp);
} else {
return this.updateValue(wp.id!, wp);
}
return this.updateValue(wp.id!, wp);
}
updateWorkPackageList(list:WorkPackageResource[], skipOnIdentical = true) {
for (var i of list) {
for (const i of list) {
const wp = i;
const workPackageId = wp.id!;
const state = this.multiState.get(workPackageId);

@ -1,8 +1,8 @@
import { APIv3ResourcePath } from "core-app/core/apiv3/paths/apiv3-resource";
import { FormResource } from "core-app/features/hal/resources/form-resource";
import { Observable } from "rxjs";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { HalPayloadHelper } from "core-app/features/hal/schemas/hal-payload.helper";
import { APIv3ResourcePath } from 'core-app/core/apiv3/paths/apiv3-resource';
import { FormResource } from 'core-app/features/hal/resources/form-resource';
import { Observable } from 'rxjs';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
export class APIv3FormResource<T extends FormResource = FormResource> extends APIv3ResourcePath<T> {
/**
@ -13,9 +13,9 @@ export class APIv3FormResource<T extends FormResource = FormResource> extends AP
return this
.halResourceService
.post<T>(
this.path,
this.extractPayload(request, schema)
);
this.path,
this.extractPayload(request, schema),
);
}
/**
@ -27,4 +27,4 @@ export class APIv3FormResource<T extends FormResource = FormResource> extends AP
public extractPayload(request:T|Object, schema:SchemaResource|null = null) {
return HalPayloadHelper.extractPayload(request, schema);
}
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -28,14 +28,14 @@
import { OPSharedModule } from 'core-app/shared/shared.module';
import { NgModule } from '@angular/core';
import { OpenprojectHalModule } from "core-app/features/hal/openproject-hal.module";
import { OpenprojectHalModule } from 'core-app/features/hal/openproject-hal.module';
@NgModule({
imports: [
// Commons
OPSharedModule,
OpenprojectHalModule,
]
],
})
export class OpenprojectApiV3Module {
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,9 +26,9 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Observable } from "rxjs";
import { ApiV3FilterBuilder, FilterOperator } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { Observable } from 'rxjs';
import { ApiV3FilterBuilder, FilterOperator } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
export type ApiV3ListFilter = [string, FilterOperator, boolean|string[]];

@ -1,25 +1,25 @@
import { Constructor } from "@angular/cdk/table";
import { SimpleResource, SimpleResourceCollection } from "core-app/core/apiv3/paths/path-resources";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { Observable } from "rxjs";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { ApiV3FilterBuilder } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Constructor } from '@angular/cdk/table';
import { SimpleResource, SimpleResourceCollection } from 'core-app/core/apiv3/paths/path-resources';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { Observable } from 'rxjs';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3FilterBuilder } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
export class APIv3ResourcePath<T = HalResource> extends SimpleResource {
readonly injector = this.apiRoot.injector;
@InjectField() halResourceService:HalResourceService;
constructor(protected apiRoot:APIV3Service,
readonly basePath:string,
readonly id:string|number,
protected parent?:APIv3ResourcePath|APIv3ResourceCollection<any, any>) {
readonly basePath:string,
readonly id:string|number,
protected parent?:APIv3ResourcePath|APIv3ResourceCollection<any, any>) {
super(basePath, id);
}
/**
* Build a singular resource from the current segment
*
@ -30,7 +30,6 @@ export class APIv3ResourcePath<T = HalResource> extends SimpleResource {
}
}
export class APIv3GettableResource<T = HalResource> extends APIv3ResourcePath<T> {
/**
* Perform a request to the HalResourceService with the current path
@ -44,12 +43,13 @@ export class APIv3GettableResource<T = HalResource> extends APIv3ResourcePath<T>
export class APIv3ResourceCollection<V, T extends APIv3GettableResource<V>> extends SimpleResourceCollection {
readonly injector = this.apiRoot.injector;
@InjectField() halResourceService:HalResourceService;
constructor(protected apiRoot:APIV3Service,
protected basePath:string,
segment:string,
protected resource?:Constructor<T>) {
protected basePath:string,
segment:string,
protected resource?:Constructor<T>) {
super(basePath, segment, resource);
}
@ -69,13 +69,11 @@ export class APIv3ResourceCollection<V, T extends APIv3GettableResource<V>> exte
return new (this.resource || APIv3GettableResource)(this.apiRoot, this.path, id, this) as T;
}
public withOptionalId(id?:string|number|null):this|T {
if (_.isNil(id)) {
return this;
} else {
return this.id(id);
}
return this.id(id);
}
/**
@ -100,7 +98,7 @@ export class APIv3ResourceCollection<V, T extends APIv3GettableResource<V>> exte
* @param params additional URL params to append
*/
public filtered<R = APIv3GettableResource<CollectionResource<V>>>(filters:ApiV3FilterBuilder, params:{ [key:string]:string } = {}, resourceClass?:Constructor<R>):R {
return this.subResource<R>('?' + filters.toParams(params), resourceClass) as R;
return this.subResource<R>(`?${filters.toParams(params)}`, resourceClass);
}
/**
@ -111,4 +109,4 @@ export class APIv3ResourceCollection<V, T extends APIv3GettableResource<V>> exte
protected subResource<R = APIv3GettableResource<HalResource>>(segment:string, cls:Constructor<R> = APIv3GettableResource as any):R {
return new cls(this.apiRoot, this.path, segment, this);
}
}
}

@ -1,4 +1,4 @@
import { Constructor } from "@angular/cdk/table";
import { Constructor } from '@angular/cdk/table';
/**
* Simple resource collection to construct paths for RESTful resources.
@ -28,9 +28,8 @@ export class SimpleResourceCollection<T = SimpleResource> {
public withOptionalId(id?:string|number):this|T {
if (_.isNil(id)) {
return this;
} else {
return this.id(id);
}
return this.id(id);
}
public toString():string {

@ -7,4 +7,4 @@ export interface IHALCollection<T> {
_embedded:{
elements:T[];
}
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,17 +26,15 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Board } from "core-app/features/boards/board/board";
import { Observable } from "rxjs";
import { map, switchMap, tap } from "rxjs/operators";
import { SchemaResource } from "core-app/features/hal/resources/schema-resource";
import { CachableAPIV3Resource } from "core-app/core/apiv3/cache/cachable-apiv3-resource";
import { MultiInputState } from "reactivestates";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { Apiv3BoardsPaths } from "core-app/core/apiv3/virtual/apiv3-boards-paths";
import { Board } from 'core-app/features/boards/board/board';
import { Observable } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { CachableAPIV3Resource } from 'core-app/core/apiv3/cache/cachable-apiv3-resource';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
import { Apiv3BoardsPaths } from 'core-app/core/apiv3/virtual/apiv3-boards-paths';
export class APIv3BoardPath extends CachableAPIV3Resource<Board> {
/**
* Perform a request to the HalResourceService with the current path
*/
@ -47,13 +45,13 @@ export class APIv3BoardPath extends CachableAPIV3Resource<Board> {
.id(this.id)
.get()
.pipe(
map(grid => {
map((grid) => {
const newBoard = new Board(grid);
newBoard.sortWidgets();
return newBoard;
})
}),
);
}
@ -68,14 +66,13 @@ export class APIv3BoardPath extends CachableAPIV3Resource<Board> {
.apiRoot
.grids
.id(board.grid)
.patch(board.grid, schema)
),
map(grid => {
.patch(board.grid, schema)),
map((grid) => {
board.grid = grid;
board.sortWidgets();
return board;
}),
this.cacheResponse()
this.cacheResponse(),
);
}
@ -86,7 +83,7 @@ export class APIv3BoardPath extends CachableAPIV3Resource<Board> {
.id(this.id)
.delete()
.pipe(
tap(() => this.cache.clearSome(this.id.toString()))
tap(() => this.cache.clearSome(this.id.toString())),
);
}
@ -98,7 +95,7 @@ export class APIv3BoardPath extends CachableAPIV3Resource<Board> {
.form
.post({})
.pipe(
map(form => form.schema)
map((form) => form.schema),
);
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,30 +26,27 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Constructor } from "@angular/cdk/table";
import { GridResource } from "core-app/features/hal/resources/grid-resource";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Observable } from "rxjs";
import { Apiv3ListParameters, listParamsString } from "core-app/core/apiv3/paths/apiv3-list-resource.interface";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { Board, BoardType } from "core-app/features/boards/board/board";
import { map, switchMap, tap } from "rxjs/operators";
import { InjectField } from "core-app/shared/helpers/angular/inject-field.decorator";
import { CurrentProjectService } from "core-app/core/current-project/current-project.service";
import { AuthorisationService } from "core-app/core/model-auth/model-auth.service";
import { CachableAPIV3Collection } from "core-app/core/apiv3/cache/cachable-apiv3-collection";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { MultiInputState } from "reactivestates";
import { APIv3BoardPath } from "core-app/core/apiv3/virtual/apiv3-board-path";
import { StateCacheService } from "core-app/core/apiv3/cache/state-cache.service";
import { GridResource } from 'core-app/features/hal/resources/grid-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { Observable } from 'rxjs';
import { Apiv3ListParameters, listParamsString } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { Board, BoardType } from 'core-app/features/boards/board/board';
import { map, switchMap, tap } from 'rxjs/operators';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
import { CachableAPIV3Collection } from 'core-app/core/apiv3/cache/cachable-apiv3-collection';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { APIv3BoardPath } from 'core-app/core/apiv3/virtual/apiv3-board-path';
import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service';
export class Apiv3BoardsPaths extends CachableAPIV3Collection<Board, APIv3BoardPath> {
@InjectField() private authorisationService:AuthorisationService;
@InjectField() private PathHelper:PathHelperService;
constructor(protected apiRoot:APIV3Service,
protected basePath:string) {
protected basePath:string) {
super(apiRoot, basePath, 'grids', APIv3BoardPath);
}
@ -62,16 +59,14 @@ export class Apiv3BoardsPaths extends CachableAPIV3Collection<Board, APIv3BoardP
.halResourceService
.get<CollectionResource<GridResource>>(this.path + listParamsString(params))
.pipe(
tap(collection => this.authorisationService.initModelAuth('boards', collection.$links)),
map(collection =>
collection.elements.map(grid => {
const board = new Board(grid);
board.sortWidgets();
this.touch(board);
tap((collection) => this.authorisationService.initModelAuth('boards', collection.$links)),
map((collection) => collection.elements.map((grid) => {
const board = new Board(grid);
board.sortWidgets();
this.touch(board);
return board;
})
)
return board;
})),
);
}
@ -96,7 +91,7 @@ export class Apiv3BoardsPaths extends CachableAPIV3Collection<Board, APIv3BoardP
return this
.createGrid(type, name, scope, actionAttribute)
.pipe(
map(grid => new Board(grid))
map((grid) => new Board(grid)),
);
}
@ -115,9 +110,9 @@ export class Apiv3BoardsPaths extends CachableAPIV3Collection<Board, APIv3BoardP
}
private createGrid(type:BoardType, name:string, scope:string, actionAttribute?:string):Observable<GridResource> {
const payload:any = _.set({ name: name }, '_links.scope.href', scope);
const payload:any = _.set({ name }, '_links.scope.href', scope);
payload.options = {
type: type,
type,
};
if (actionAttribute) {
@ -130,12 +125,10 @@ export class Apiv3BoardsPaths extends CachableAPIV3Collection<Board, APIv3BoardP
.form
.post(payload)
.pipe(
switchMap((form) => {
return this
.apiRoot
.grids
.post(form.payload.$source);
})
switchMap((form) => this
.apiRoot
.grids
.post(form.payload.$source)),
);
}
}

@ -300,7 +300,7 @@ RB.Model = (function ($) {
},
getType: function () {
throw "Child objects must override getType()";
throw new Error("Child objects must override getType()");
},
handleClick: function (e) {
@ -346,7 +346,7 @@ RB.Model = (function ($) {
},
markIfClosed: function () {
throw "Child objects must override markIfClosed()";
throw new Error("Child objects must override markIfClosed()");
},
markSaving: function () {
@ -393,7 +393,7 @@ RB.Model = (function ($) {
},
saveDirectives: function () {
throw "Child object must implement saveDirectives()";
throw new Error("Child object must implement saveDirectives()");
},
saveEdits: function () {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,90 +27,90 @@
//++
// Loaded dynamically when path matches
(function ($, undefined) {
var global_roles = {
init: function () {
if (global_roles.script_applicable()) {
global_roles.toggle_forms_on_click();
global_roles.activation_and_visibility_based_on_checked($('#global_role'));
(function ($) {
const globalRoles = {
init() {
if (globalRoles.script_applicable()) {
globalRoles.toggle_forms_on_click();
globalRoles.activation_and_visibility_based_on_checked($('#global_role'));
}
},
toggle_forms_on_click: function () {
$('#global_role').on("click", global_roles.toggle_forms);
toggle_forms_on_click() {
$('#global_role').on('click', this.toggle_forms);
},
toggle_forms: function (event:any) {
global_roles.activation_and_visibility_based_on_checked(this);
toggle_forms() {
globalRoles.activation_and_visibility_based_on_checked(this);
},
activation_and_visibility_based_on_checked: function (element:any) {
if ($(element).prop("checked")) {
global_roles.show_global_forms();
global_roles.hide_member_forms();
global_roles.enable_global_forms();
global_roles.disable_member_forms();
activation_and_visibility_based_on_checked(element:any) {
if ($(element).prop('checked')) {
globalRoles.show_global_forms();
globalRoles.hide_member_forms();
globalRoles.enable_global_forms();
globalRoles.disable_member_forms();
} else {
global_roles.show_member_forms();
global_roles.hide_global_forms();
global_roles.disable_global_forms();
global_roles.enable_member_forms();
globalRoles.show_member_forms();
globalRoles.hide_global_forms();
globalRoles.disable_global_forms();
globalRoles.enable_member_forms();
}
},
show_global_forms: function () {
show_global_forms() {
$('#global_permissions').show();
},
show_member_forms: function () {
show_member_forms() {
$('#member_attributes').show();
$('#member_permissions').show();
},
hide_global_forms: function () {
hide_global_forms() {
$('#global_permissions').hide();
},
hide_member_forms: function () {
hide_member_forms() {
$('#member_attributes').hide();
$('#member_permissions').hide();
},
enable_global_forms: function () {
$('#global_attributes input, #global_attributes input, #global_permissions input').each(function (ix, el) {
global_roles.enable_element(el);
enable_global_forms() {
$('#global_attributes input, #global_attributes input, #global_permissions input').each((ix, el) => {
globalRoles.enable_element(el);
});
},
enable_member_forms: function () {
$('#member_attributes input, #member_attributes input, #member_permissions input').each(function (ix, el) {
global_roles.enable_element(el);
enable_member_forms() {
$('#member_attributes input, #member_attributes input, #member_permissions input').each((ix, el) => {
globalRoles.enable_element(el);
});
},
enable_element: function (element:any) {
enable_element(element:any) {
$(element).prop('disabled', false);
},
disable_global_forms: function () {
$('#global_attributes input, #global_attributes input, #global_permissions input').each(function (ix, el) {
global_roles.disable_element(el);
disable_global_forms() {
$('#global_attributes input, #global_attributes input, #global_permissions input').each((ix, el) => {
globalRoles.disable_element(el);
});
},
disable_member_forms: function () {
$('#member_attributes input, #member_attributes input, #member_permissions input').each(function (ix, el) {
global_roles.disable_element(el);
disable_member_forms() {
$('#member_attributes input, #member_attributes input, #member_permissions input').each((ix, el) => {
globalRoles.disable_element(el);
});
},
disable_element: function (element:any) {
disable_element(element:any) {
$(element).prop('disabled', true);
},
script_applicable: function () {
script_applicable() {
return $('body.controller-roles.action-new, body.controller-roles.action-create').length === 1;
}
},
};
$(document).ready(global_roles.init);
$(document).ready(globalRoles.init);
}(jQuery));

@ -1,63 +1,64 @@
import 'core-vendor/qrcode-min';
declare var QRCode:any;
declare let QRCode:any;
jQuery(function ($) {
$('#submit_otp').submit(function() {
$('.ajax_form').find("input, radio").attr('disabled', 'disabled');
jQuery(($) => {
$('#submit_otp').submit(() => {
$('.ajax_form').find('input, radio').attr('disabled', 'disabled');
});
$('#toggle_resend_form').click(function(){
$('#toggle_resend_form').click(() => {
$('#resend_otp_container').toggle();
return false;
});
$('.qr-code-element').each(function() {
var el = $(this);
$('.qr-code-element').each(function () {
const el = $(this);
new QRCode(
el[0],
{
text: el.data('value'),
width: 220,
height: 220
}
height: 220,
},
);
});
$('.ajax_form').submit(function() {
$('#submit_otp').find("input").attr('disabled', 'disabled');
var form = $(this),
submit_button = form.find("input[type=submit]");
$.ajax({ url: form.attr('action'),
$('.ajax_form').submit(function () {
$('#submit_otp').find('input').attr('disabled', 'disabled');
const form = $(this);
const submit_button = form.find('input[type=submit]');
$.ajax({
url: form.attr('action'),
type: 'post',
data: form.serialize(),
beforeSend: function() {
beforeSend() {
submit_button.attr('disabled', 'disabled');
submit_button.toggleClass('submitting');
$('.flash.notice').toggle();
},
complete: function(response) {
complete(response) {
submit_button.removeAttr('disabled');
$('#submit_otp').find("input").removeAttr('disabled');
$('#submit_otp').find('input').removeAttr('disabled');
$('.flash.notice a').html(response.responseText);
$('form#resend_otp, #toggle_resend_form, .flash.notice').toggle();
submit_button.toggleClass('submitting');
}
},
});
return false;
});
$('#print_2fa_backup_codes').click(function() {
$('#print_2fa_backup_codes').click(() => {
window.print();
});
if ($('#download_2fa_backup_codes').length) {
var text = '';
$('.two-factor-authentication--backup-codes li').each(function() {
text += this.textContent + "\n";
let text = '';
$('.two-factor-authentication--backup-codes li').each(function () {
text += `${this.textContent}\n`;
});
var element = $('#download_2fa_backup_codes');
element.attr('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
const element = $('#download_2fa_backup_codes');
element.attr('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);
element.attr('download', 'backup-codes.txt');
}
});

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,13 +27,13 @@
//++
import { NgModule } from '@angular/core';
import { OpenprojectModalModule } from "core-app/shared/components/modal/modal.module";
import { OpModalWrapperAugmentService } from "core-app/shared/components/modal/modal-wrapper-augment.service";
import { PathScriptAugmentService } from "core-app/core/augmenting/services/path-script.augment.service";
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
import { OpModalWrapperAugmentService } from 'core-app/shared/components/modal/modal-wrapper-augment.service';
import { PathScriptAugmentService } from 'core-app/core/augmenting/services/path-script.augment.service';
@NgModule({
imports: [ OpenprojectModalModule ],
providers: [ PathScriptAugmentService ],
imports: [OpenprojectModalModule],
providers: [PathScriptAugmentService],
})
export class OpenprojectAugmentingModule {
constructor(modalWrapper:OpModalWrapperAugmentService,
@ -43,4 +43,3 @@ export class OpenprojectAugmentingModule {
pathScript.loadRequiredScripts();
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,14 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Inject, Injectable } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { debugLog } from "core-app/shared/helpers/debug_output";
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { debugLog } from 'core-app/shared/helpers/debug_output';
@Injectable({ providedIn: 'root' })
export class PathScriptAugmentService {
constructor(@Inject(DOCUMENT) protected documentElement:Document) {
}
@ -49,8 +47,8 @@ export class PathScriptAugmentService {
const matches = this.documentElement.querySelectorAll<HTMLMetaElement>('meta[name="required_script"]');
for (let i = 0; i < matches.length; ++i) {
const name = matches[i].content;
debugLog("Loading required script " + name);
import('../dynamic-scripts/' + name);
debugLog(`Loading required script ${name}`);
import(`../dynamic-scripts/${name}`);
}
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,29 +26,29 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {HalResource} from "core-app/features/hal/resources/hal-resource";
import {Observable} from "rxjs";
import {HalResourceService} from "core-app/features/hal/services/hal-resource.service";
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { Observable } from 'rxjs';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
@Injectable({ providedIn: 'root' })
export class OpenProjectBackupService {
constructor(protected http:HttpClient,
protected halResource:HalResourceService) {
protected halResource:HalResourceService) {
}
public triggerBackup(backupToken:string, includeAttachments:boolean=true):Observable<HalResource> {
public triggerBackup(backupToken:string, includeAttachments = true):Observable<HalResource> {
return this
.http
.request<HalResource>(
"post",
"/api/v3/backups",
{
body: { backupToken: backupToken, attachments: includeAttachments },
withCredentials: true,
responseType: "json" as any
}
);
'post',
'/api/v3/backups',
{
body: { backupToken, attachments: includeAttachments },
withCredentials: true,
responseType: 'json' as any,
},
);
}
}

@ -1,10 +1,9 @@
import { Inject, Injectable } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class BrowserDetector {
constructor (@Inject(DOCUMENT) private documentElement:Document) {
constructor(@Inject(DOCUMENT) private documentElement:Document) {
}
/**
@ -25,5 +24,4 @@ export class BrowserDetector {
private hasBodyClass(name:string):boolean {
return this.documentElement.body.classList.contains(name);
}
}

@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class DeviceService {
public mobileWidthTreshold = 680;
public get isMobile():boolean {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,9 +27,9 @@
//++
import { Injectable } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ConfigurationResource } from "core-app/features/hal/resources/configuration-resource";
import * as moment from "moment";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { ConfigurationResource } from 'core-app/features/hal/resources/configuration-resource';
import * as moment from 'moment';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
@Injectable({ providedIn: 'root' })
export class ConfigurationService {
@ -37,10 +37,11 @@ export class ConfigurationService {
// TODO: this currently saves the request between page reloads,
// but could easily be stored in localStorage
private configuration:ConfigurationResource;
public initialized:Promise<boolean>;
public constructor(readonly I18n:I18nService,
readonly apiV3Service:APIV3Service) {
readonly apiV3Service:APIV3Service) {
this.initialized = this.loadConfiguration().then(() => true).catch(() => false);
}
@ -103,9 +104,8 @@ export class ConfigurationService {
public startOfWeek() {
if (this.startOfWeekPresent()) {
return this.systemPreference('startOfWeek');
} else {
return moment.localeData(I18n.locale).firstDayOfWeek();
}
return moment.localeData(I18n.locale).firstDayOfWeek();
}
private loadConfiguration() {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,21 +26,19 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
/* jshint expr: true */
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { CurrentProjectService } from './current-project.service';
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
describe('currentProject service', function() {
describe('currentProject service', () => {
let element:JQuery;
let currentProject:CurrentProjectService;
const apiV3Stub:any = {
projects: {
id: (id:string) => {
return { toString: () => '/api/v3/projects/' + id };
}
}
id: (id:string) => ({ toString: () => `/api/v3/projects/${id}` }),
},
};
beforeEach(() => {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,16 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable } from "@angular/core";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { Injectable } from '@angular/core';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
@Injectable({ providedIn: 'root' })
export class CurrentProjectService {
private current:{ id:string, identifier:string, name:string };
constructor(private PathHelper:PathHelperService,
private apiV3Service:APIV3Service) {
private apiV3Service:APIV3Service) {
this.detect();
}
@ -88,7 +88,7 @@ export class CurrentProjectService {
this.current = {
id: element.dataset.projectId!,
name: element.dataset.projectName!,
identifier: element.dataset.projectIdentifier!
identifier: element.dataset.projectIdentifier!,
};
}
}

@ -1,10 +1,8 @@
import { Injector, NgModule } from "@angular/core";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { Injector, NgModule } from '@angular/core';
import { CurrentUserService } from "./current-user.service";
import { CurrentUserStore } from "./current-user.store";
import { CurrentUserQuery } from "./current-user.query";
import { CurrentUserService } from './current-user.service';
import { CurrentUserStore } from './current-user.store';
import { CurrentUserQuery } from './current-user.query';
export function bootstrapModule(injector:Injector) {
const currentUserService = injector.get(CurrentUserService);

@ -1,21 +1,16 @@
import { Injectable } from '@angular/core';
import { filterNilValue, Query } from '@datorama/akita';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import {
CurrentUserStore,
CurrentUserState,
CurrentUser,
} from './current-user.store';
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { CurrentUserState, CurrentUserStore } from './current-user.store';
@Injectable()
export class CurrentUserQuery extends Query<CurrentUserState> {
constructor(protected store: CurrentUserStore) {
constructor(protected store:CurrentUserStore) {
super(store);
}
isLoggedIn$ = this.select(state => !!state.id);
isLoggedIn$ = this.select((state) => !!state.id);
user$ = this.select(({ id, name, mail }) => ({ id, name, mail }));
capabilities$ = this.select('capabilities').pipe(filterNilValue());
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,17 +26,16 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
/* jshint expr: true */
import { TestBed, getTestBed } from '@angular/core/testing';
import { getTestBed, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { States } from "core-app/core/states/states.service";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { States } from 'core-app/core/states/states.service';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { CurrentUserService } from './current-user.service';
import { CurrentUserStore } from "./current-user.store";
import { CurrentUserQuery } from "./current-user.query";
import { CurrentUser } from './current-user.store';
import { CurrentUser, CurrentUserStore } from './current-user.store';
import { CurrentUserQuery } from './current-user.query';
const globalCapability = {
_type: 'Capability',
@ -122,7 +121,7 @@ const projectCapabilityp53Update = {
},
};
describe('CurrentUserService', function () {
describe('CurrentUserService', () => {
let injector:TestBed;
let currentUserService:CurrentUserService;
let httpMock:HttpTestingController;
@ -171,8 +170,7 @@ describe('CurrentUserService', function () {
},
});
});
}
};
afterEach(() => {
httpMock.verify();

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,30 +26,34 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable } from "@angular/core";
import { of, forkJoin } from 'rxjs';
import { take, map, mergeMap, distinctUntilChanged, tap } from 'rxjs/operators';
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { CollectionResource } from "core-app/features/hal/resources/collection-resource";
import { CurrentUserStore, CurrentUser } from "./current-user.store";
import { CurrentUserQuery } from "./current-user.query";
import { FilterOperator } from "core-app/shared/helpers/api-v3/api-v3-filter-builder";
import { Injectable } from '@angular/core';
import { forkJoin, of } from 'rxjs';
import {
distinctUntilChanged, map, mergeMap, take,
} from 'rxjs/operators';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { CapabilityResource } from 'core-app/features/hal/resources/capability-resource';
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
import { FilterOperator } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
import { CurrentUser, CurrentUserStore } from './current-user.store';
import { CurrentUserQuery } from './current-user.query';
@Injectable({ providedIn: 'root' })
export class CurrentUserService {
private PAGE_FETCH_SIZE = 1000;
constructor(
private apiV3Service: APIV3Service,
private currentUserStore: CurrentUserStore,
private currentUserQuery: CurrentUserQuery,
private apiV3Service:APIV3Service,
private currentUserStore:CurrentUserStore,
private currentUserQuery:CurrentUserQuery,
) {
this.setupLegacyDataListeners();
}
public capabilities$ = this.currentUserQuery.capabilities$;
public isLoggedIn$ = this.currentUserQuery.isLoggedIn$;
public user$ = this.currentUserQuery.user$;
/**
@ -57,8 +61,8 @@ export class CurrentUserService {
*
* This refetches the global and current project capabilities
*/
public setUser(user: CurrentUser) {
this.currentUserStore.update(state => ({
public setUser(user:CurrentUser) {
this.currentUserStore.update((state) => ({
...state,
...user,
}));
@ -72,7 +76,7 @@ export class CurrentUserService {
public fetchCapabilities(contexts:string[] = []) {
this.user$.pipe(take(1)).subscribe((user) => {
if (!user.id) {
this.currentUserStore.update(state => ({
this.currentUserStore.update((state) => ({
...state,
capabilities: [],
}));
@ -80,56 +84,56 @@ export class CurrentUserService {
return;
}
const filters: [string, FilterOperator, string[]][] = [ ['principal', '=', [user.id]] ];
const filters:[string, FilterOperator, string[]][] = [['principal', '=', [user.id]]];
if (contexts.length) {
filters.push([ 'context', '=', contexts.map(context => context === 'global' ? 'g' : `p${context}`) ]);
filters.push(['context', '=', contexts.map((context) => (context === 'global' ? 'g' : `p${context}`))]);
}
this.apiV3Service.capabilities.list({
pageSize: this.PAGE_FETCH_SIZE,
filters,
})
.pipe(
mergeMap((data: CollectionResource<CapabilityResource>) => {
.pipe(
mergeMap((data:CollectionResource<CapabilityResource>) => {
// The data we've loaded might not contain all capabilities. Some responses might have thousands of
// capabilites, and our page size is restricted. If this is the case, we branch out and sent out parallel
// requests for each of the other pages.
if (data.total > this.PAGE_FETCH_SIZE) {
const remaining = data.total - this.PAGE_FETCH_SIZE;
const pagesRemaining = Math.ceil(remaining / this.PAGE_FETCH_SIZE);
const calls = (new Array(pagesRemaining))
.fill(null)
.map((_, i) => this.apiV3Service.capabilities.list({
pageSize: this.PAGE_FETCH_SIZE,
offset: i + 2, // Page offsets are 1-indexed, and we already fetched the first page
filters,
}));
// Branch out and fetch all remaining pages in parallel.
// Afterwards, merge the resulting list
return forkJoin(...calls).pipe(
map(
(results: CollectionResource<CapabilityResource>[]) => results.reduce(
(acc, next) => acc.concat(next.elements),
data.elements,
if (data.total > this.PAGE_FETCH_SIZE) {
const remaining = data.total - this.PAGE_FETCH_SIZE;
const pagesRemaining = Math.ceil(remaining / this.PAGE_FETCH_SIZE);
const calls = (new Array(pagesRemaining))
.fill(null)
.map((_, i) => this.apiV3Service.capabilities.list({
pageSize: this.PAGE_FETCH_SIZE,
offset: i + 2, // Page offsets are 1-indexed, and we already fetched the first page
filters,
}));
// Branch out and fetch all remaining pages in parallel.
// Afterwards, merge the resulting list
return forkJoin(...calls).pipe(
map(
(results:CollectionResource<CapabilityResource>[]) => results.reduce(
(acc, next) => acc.concat(next.elements),
data.elements,
),
),
),
);
}
// The current page is the only page, return the results.
return of(data.elements);
}),
)
.subscribe((capabilities) => {
this.currentUserStore.update(state => ({
...state,
capabilities: [
...capabilities,
...(state.capabilities || []).filter(cap => !!capabilities.find(newCap => newCap.id === cap.id)),
],
}));
});
);
}
// The current page is the only page, return the results.
return of(data.elements);
}),
)
.subscribe((capabilities) => {
this.currentUserStore.update((state) => ({
...state,
capabilities: [
...capabilities,
...(state.capabilities || []).filter((cap) => !!capabilities.find((newCap) => newCap.id === cap.id)),
],
}));
});
});
return this.currentUserQuery.capabilities$;
@ -138,37 +142,35 @@ export class CurrentUserService {
/**
* Returns the users' capabilities filtered by context
*/
public capabilitiesForContext$(contextId: string) {
public capabilitiesForContext$(contextId:string) {
return this.capabilities$.pipe(
map((capabilities) => capabilities.filter(cap => cap.context.href.endsWith(`/${contextId}`))),
map((capabilities) => capabilities.filter((cap) => cap.context.href.endsWith(`/${contextId}`))),
distinctUntilChanged(),
);
}
/**
* Returns an Observable<boolean> indicating whether the user has the required capabilities in the provided context.
* Returns an Observable<boolean> indicating whether the user has the required capabilities in the provided context.
*/
public hasCapabilities$(action: string|string[], contextId: string = 'global') {
public hasCapabilities$(action:string|string[], contextId = 'global') {
const actions = _.castArray(action);
return this.capabilitiesForContext$(contextId).pipe(
map((capabilities) => actions.reduce(
(acc, action) => {
return acc && !!capabilities.find(cap => cap.action.href.endsWith(`/api/v3/actions/${action}`));
},
(acc, contextAction) => acc && !!capabilities.find((cap) => cap.action.href.endsWith(`/api/v3/actions/${contextAction}`)),
capabilities.length > 0,
)),
distinctUntilChanged()
distinctUntilChanged(),
);
}
/**
* Returns an Observable<boolean> indicating whether the user has any of the required capabilities in the provided context.
* Returns an Observable<boolean> indicating whether the user has any of the required capabilities in the provided context.
*/
public hasAnyCapabilityOf$(actions: string|string[], contextId: string = 'global') {
public hasAnyCapabilityOf$(actions:string|string[], contextId = 'global') {
const actionsToFilter = _.castArray(actions);
return this.capabilitiesForContext$(contextId).pipe(
map((capabilities) => capabilities.reduce(
(acc, cap) => acc || !!actionsToFilter.find(action => cap.action.href.endsWith(`/api/v3/actions/${action}`)),
(acc, cap) => acc || !!actionsToFilter.find((action) => cap.action.href.endsWith(`/api/v3/actions/${action}`)),
false,
)),
distinctUntilChanged(),
@ -177,19 +179,19 @@ export class CurrentUserService {
// Everything below this is deprecated legacy interfacing and should not be used
private setupLegacyDataListeners() {
this.currentUserQuery.user$.subscribe(user => this._user = user);
this.currentUserQuery.isLoggedIn$.subscribe(isLoggedIn => this._isLoggedIn = isLoggedIn);
this.currentUserQuery.user$.subscribe((user) => (this._user = user));
this.currentUserQuery.isLoggedIn$.subscribe((isLoggedIn) => (this._isLoggedIn = isLoggedIn));
}
private _isLoggedIn = false;
/** @deprecated Use the store mechanism `currentUserQuery.isLoggedIn$` */
public get isLoggedIn() {
return this._isLoggedIn;
}
private _user: CurrentUser = {
private _user:CurrentUser = {
id: null,
name: null,
mail: null,

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,21 +26,21 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable } from "@angular/core";
import { Injectable } from '@angular/core';
import { Store, StoreConfig } from '@datorama/akita';
import { CapabilityResource } from "core-app/features/hal/resources/capability-resource";
import { CapabilityResource } from 'core-app/features/hal/resources/capability-resource';
export interface CurrentUser {
id: string|null;
name: string|null;
mail: string|null;
id:string|null;
name:string|null;
mail:string|null;
}
export interface CurrentUserState extends CurrentUser {
capabilities: CapabilityResource[]|null;
capabilities:CapabilityResource[]|null;
}
export function createInitialState(): CurrentUserState {
export function createInitialState():CurrentUserState {
return {
id: null,
name: null,

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,7 +26,7 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
/* jshint expr: true */
import { TestBed } from '@angular/core/testing';
import { HttpClientModule } from '@angular/common/http';
@ -35,8 +35,7 @@ import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
describe('TimezoneService', function () {
describe('TimezoneService', () => {
const TIME = '2013-02-08T09:30:26';
const DATE = '2013-02-08';
let timezoneService:TimezoneService;
@ -44,57 +43,56 @@ describe('TimezoneService', function () {
const compile = (timezone?:string) => {
const ConfigurationServiceStub = {
isTimezoneSet: () => !!timezone,
timezone: () => timezone
timezone: () => timezone,
};
TestBed.configureTestingModule({
imports: [
HttpClientModule
HttpClientModule,
],
providers: [
{ provide: I18nService, useValue: {} },
{ provide: ConfigurationService, useValue: ConfigurationServiceStub },
PathHelperService,
TimezoneService,
]
],
});
timezoneService = TestBed.inject(TimezoneService);
};
describe('without time zone set', function () {
describe('without time zone set', () => {
beforeEach(() => {
compile();
});
describe('#parseDatetime', function () {
it('is UTC', function () {
var time = timezoneService.parseDatetime(TIME);
describe('#parseDatetime', () => {
it('is UTC', () => {
const time = timezoneService.parseDatetime(TIME);
expect(time.utcOffset()).toEqual(0);
expect(time.format('HH:mm')).toEqual('09:30');
});
it('has no time information', function () {
var time = timezoneService.parseDate(DATE);
it('has no time information', () => {
const time = timezoneService.parseDate(DATE);
expect(time.format('HH:mm')).toEqual('00:00');
});
});
});
describe('with time zone set', function () {
describe('with time zone set', () => {
beforeEach(() => {
compile('America/Vancouver');
});
describe('Non-UTC timezone', function () {
it('is in the given timezone' , function () {
describe('Non-UTC timezone', () => {
it('is in the given timezone', () => {
const date = timezoneService.parseDatetime(TIME);
expect(date.format('HH:mm')).toEqual('01:30');
});
it('has local time zone', function () {
expect(timezoneService.ConfigurationService.timezone()).toEqual('America/Vancouver');
it('has local time zone', () => {
expect(timezoneService.configurationService.timezone()).toEqual('America/Vancouver');
});
});
});

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -34,8 +34,8 @@ import { Moment } from 'moment';
@Injectable({ providedIn: 'root' })
export class TimezoneService {
constructor(readonly ConfigurationService:ConfigurationService,
readonly I18n:I18nService) {
constructor(readonly configurationService:ConfigurationService,
readonly I18n:I18nService) {
this.setupLocale();
}
@ -48,11 +48,11 @@ export class TimezoneService {
* a local date time moment object.
*/
public parseDatetime(datetime:string, format?:string):Moment {
var d = moment.utc(datetime, format);
const d = moment.utc(datetime, format);
if (this.ConfigurationService.isTimezoneSet()) {
if (this.configurationService.isTimezoneSet()) {
d.local();
d.tz(this.ConfigurationService.timezone());
d.tz(this.configurationService.timezone());
}
return d;
@ -73,13 +73,13 @@ export class TimezoneService {
* @returns {Moment}
*/
public parseLocalDateTime(date:string, format?:string) {
var result;
format = format || this.getTimeFormat();
let result;
const timeFormat = format || this.getTimeFormat();
if (this.ConfigurationService.isTimezoneSet()) {
result = moment.tz(date, format!, this.ConfigurationService.timezone());
if (this.configurationService.isTimezoneSet()) {
result = moment.tz(date, timeFormat, this.configurationService.timezone());
} else {
result = moment(date, format);
result = moment(date, timeFormat);
}
result.utc();
@ -103,7 +103,7 @@ export class TimezoneService {
}
public formattedDate(date:string) {
var d = this.parseDate(date);
const d = this.parseDate(date);
return d.format(this.getDateFormat());
}
@ -132,8 +132,8 @@ export class TimezoneService {
}
public formattedDatetime(datetimeString:string) {
var c = this.formattedDatetimeComponents(datetimeString);
return c[0] + ' ' + c[1];
const c = this.formattedDatetimeComponents(datetimeString);
return `${c[0]} ${c[1]}`;
}
public formattedRelativeDateTime(datetimeString:string) {
@ -142,10 +142,10 @@ export class TimezoneService {
}
public formattedDatetimeComponents(datetimeString:string) {
var d = this.parseDatetime(datetimeString);
const d = this.parseDatetime(datetimeString);
return [
d.format(this.getDateFormat()),
d.format(this.getTimeFormat())
d.format(this.getTimeFormat()),
];
}
@ -174,15 +174,15 @@ export class TimezoneService {
}
public isValid(date:string, dateFormat:string) {
var format = dateFormat || this.getDateFormat();
const format = dateFormat || this.getDateFormat();
return moment(date, [format], true).isValid();
}
public getDateFormat() {
return this.ConfigurationService.dateFormatPresent() ? this.ConfigurationService.dateFormat() : 'L';
return this.configurationService.dateFormatPresent() ? this.configurationService.dateFormat() : 'L';
}
public getTimeFormat() {
return this.ConfigurationService.timeFormatPresent() ? this.ConfigurationService.timeFormat() : 'LT';
return this.configurationService.timeFormatPresent() ? this.configurationService.timeFormat() : 'LT';
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,12 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import {Inject, Injectable} from '@angular/core';
import {DOCUMENT} from "@angular/common";
import {enterpriseEditionUrl} from "core-app/core/setup/globals/constants.const";
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { enterpriseEditionUrl } from 'core-app/core/setup/globals/constants.const';
@Injectable({ providedIn: 'root' })
export class BannersService {
private readonly _banners:boolean = true;
constructor(@Inject(DOCUMENT) protected documentElement:Document) {
@ -43,7 +42,7 @@ export class BannersService {
return this._banners;
}
public getEnterPriseEditionUrl({ referrer, hash }:{referrer?:string, hash?:string} = {}) {
public getEnterPriseEditionUrl({ referrer, hash }:{ referrer?:string, hash?:string } = {}) {
const url = new URL(enterpriseEditionUrl);
if (referrer) {
url.searchParams.set('op_referrer', referrer);

@ -1,4 +1,4 @@
import * as Sentry from "@sentry/angular";
import { Integrations } from "@sentry/tracing";
import * as Sentry from '@sentry/angular';
import { Integrations } from '@sentry/tracing';
export { Sentry, Integrations };
export { Sentry, Integrations };

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,8 +26,10 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Hub, Severity, Scope, Event as SentryEvent } from "@sentry/types";
import { environment } from "../../../../environments/environment";
import {
Event as SentryEvent, Hub, Scope, Severity,
} from '@sentry/types';
import { environment } from '../../../../environments/environment';
export type ScopeCallback = (scope:Scope) => void;
export type MessageSeverity = 'fatal'|'error'|'warning'|'log'|'info'|'debug';
@ -57,7 +59,6 @@ interface QueuedMessage {
}
export class SentryReporter implements ErrorReporter {
private contextCallbacks:ScopeCallback[] = [];
private messageStack:QueuedMessage[] = [];
@ -67,7 +68,7 @@ export class SentryReporter implements ErrorReporter {
private client:Hub;
constructor() {
const sentryElement = document.querySelector('meta[name=openproject_sentry]') as HTMLElement|null;
const sentryElement = document.querySelector('meta[name=openproject_sentry]') as HTMLElement;
if (sentryElement !== null) {
this.loadSentry(sentryElement);
} else {
@ -84,9 +85,9 @@ export class SentryReporter implements ErrorReporter {
import('./sentry-dependency').then((imported) => {
const sentry = imported.Sentry;
sentry.init({
dsn: dsn,
dsn,
debug: !environment.production,
release: 'op-frontend@' + version,
release: `op-frontend@${version}`,
environment: environment.production ? 'production' : 'development',
// Integrations
@ -94,13 +95,13 @@ export class SentryReporter implements ErrorReporter {
tracesSampler: (samplingContext) => {
switch (samplingContext.transactionContext.op) {
case 'op':
case 'navigation':
case 'op':
case 'navigation':
// Trace 1% of page loads and navigation events
return Math.min(0.01 * traceFactor, 1.0);
default:
return Math.min(0.01 * traceFactor, 1.0);
default:
// Trace 0.1% of requests
return Math.min(0.001 * traceFactor, 1.0);
return Math.min(0.001 * traceFactor, 1.0);
}
},
@ -159,7 +160,7 @@ export class SentryReporter implements ErrorReporter {
if (this.client) {
/** Add to global context as well */
callbacks.forEach(cb => this.client.configureScope(cb));
callbacks.forEach((cb) => this.client.configureScope(cb));
}
}
@ -172,7 +173,7 @@ export class SentryReporter implements ErrorReporter {
if (this.sentryConfigured) {
this.messageStack.push({ type, args });
} else {
console.log("[ErrorReporter] Would queue sentry message %O %O, but is not configured.", type, args);
console.log('[ErrorReporter] Would queue sentry message %O %O, but is not configured.', type, args);
}
}
@ -187,7 +188,7 @@ export class SentryReporter implements ErrorReporter {
scope.setExtra('url_query', window.location.search);
/** Execute callbacks */
this.contextCallbacks.forEach(cb => cb(scope));
this.contextCallbacks.forEach((cb) => cb(scope));
}
/**
@ -199,10 +200,10 @@ export class SentryReporter implements ErrorReporter {
private filterEvent(event:SentryEvent):SentryEvent|null {
const unsupportedBrowser = document.body.classList.contains('-unsupported-browser');
if (unsupportedBrowser) {
console.warn("Browser is not supported, skipping sentry reporting completely.");
console.warn('Browser is not supported, skipping sentry reporting completely.');
return null;
}
return event;
}
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,7 +27,6 @@
//++
export class ExpressionService {
// This is what returned by rails-angular-xss when it discovers double open curly braces
// See https://github.com/opf/rails-angular-xss for more information.
public static get UNESCAPED_EXPRESSION() {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,12 +26,14 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable } from "@angular/core";
import { HttpEvent, HttpResponse } from "@angular/common/http";
import { from, Observable, of } from "rxjs";
import { share, switchMap } from "rxjs/operators";
import { OpenProjectFileUploadService, UploadBlob, UploadFile, UploadInProgress } from './op-file-upload.service';
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { Injectable } from '@angular/core';
import { HttpEvent, HttpResponse } from '@angular/common/http';
import { from, Observable, of } from 'rxjs';
import { share, switchMap } from 'rxjs/operators';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import {
OpenProjectFileUploadService, UploadBlob, UploadFile, UploadInProgress,
} from './op-file-upload.service';
interface PrepareUploadResult {
url:string;
@ -51,47 +53,47 @@ export class OpenProjectDirectFileUploadService extends OpenProjectFileUploadSer
const observable = from(this.getDirectUploadFormFrom(url, file))
.pipe(
switchMap(this.uploadToExternal(file, method, responseType)),
share()
share(),
);
return [file, observable] as UploadInProgress;
}
private uploadToExternal(file:UploadFile|UploadBlob, method:string, responseType:string):(result:PrepareUploadResult) => Observable<HttpEvent<unknown>> {
return result => {
return (result) => {
result.form.append('file', file, file.customName || file.name);
return this
.http
.request<HalResource>(
method,
result.url,
{
body: result.form,
// Observe the response, not the body
observe: 'events',
// This is important as the CORS policy for the bucket is * and you can't use credentals then,
// besides we don't need them here anyway.
withCredentials: false,
responseType: responseType as any,
// Subscribe to progress events. subscribe() will fire multiple times!
reportProgress: true
}
)
method,
result.url,
{
body: result.form,
// Observe the response, not the body
observe: 'events',
// This is important as the CORS policy for the bucket is * and you can't use credentals then,
// besides we don't need them here anyway.
withCredentials: false,
responseType: responseType as any,
// Subscribe to progress events. subscribe() will fire multiple times!
reportProgress: true,
},
)
.pipe(switchMap(this.finishUpload(result)));
};
}
private finishUpload(result:PrepareUploadResult):(result:HttpEvent<unknown>) => Observable<HttpEvent<unknown>> {
return event => {
return (event) => {
if (event instanceof HttpResponse) {
return this
.http
.get(
result.response._links.completeUpload.href,
{
observe: 'response'
}
observe: 'response',
},
);
}
@ -106,7 +108,7 @@ export class OpenProjectDirectFileUploadService extends OpenProjectFileUploadSer
description: file.description,
fileName: file.customName || file.name,
fileSize: file.size,
contentType: file.type
contentType: file.type,
};
/*
@ -124,14 +126,14 @@ export class OpenProjectDirectFileUploadService extends OpenProjectFileUploadSer
const result = this
.http
.request<HalResource>(
"post",
url,
{
body: formData,
withCredentials: true,
responseType: "json" as any
}
)
'post',
url,
{
body: formData,
withCredentials: true,
responseType: 'json' as any,
},
)
.toPromise()
.then((res) => {
const form = new FormData();
@ -140,7 +142,7 @@ export class OpenProjectDirectFileUploadService extends OpenProjectFileUploadSer
form.append(key, value);
});
return { url: res._links.addAttachment.href, form: form, response: res };
return { url: res._links.addAttachment.href, form, response: res };
});
return result;

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,13 +26,13 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { OpenProjectDirectFileUploadService } from 'core-app/core/file-upload/op-direct-file-upload.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { getTestBed, TestBed } from '@angular/core/testing';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { States } from 'core-app/core/states/states.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { OpenProjectFileUploadService, UploadFile, UploadResult } from './op-file-upload.service';
import { OpenProjectDirectFileUploadService } from "core-app/core/file-upload/op-direct-file-upload.service";
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { getTestBed, TestBed } from "@angular/core/testing";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { States } from "core-app/core/states/states.service";
import { I18nService } from "core-app/core/i18n/i18n.service";
describe('opFileUpload service', () => {
let injector:TestBed;
@ -47,8 +47,8 @@ describe('opFileUpload service', () => {
I18nService,
OpenProjectFileUploadService,
OpenProjectDirectFileUploadService,
HalResourceService
]
HalResourceService,
],
});
injector = getTestBed();
@ -61,16 +61,16 @@ describe('opFileUpload service', () => {
});
describe('when uploading multiple files', () => {
var result:UploadResult;
const file:UploadFile = new File([ JSON.stringify({
let result:UploadResult;
const file:UploadFile = new File([JSON.stringify({
name: 'name',
description: 'description'
description: 'description',
})], 'name');
beforeEach(() => {
result = service.upload('/my/api/path', [file, file]);
httpMock.match(`/my/api/path`).forEach((req) => {
expect(req.request.method).toBe("POST");
httpMock.match('/my/api/path').forEach((req) => {
expect(req.request.method).toBe('POST');
req.flush({});
});
});

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,19 +26,20 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable } from "@angular/core";
import { HttpClient, HttpEvent, HttpEventType, HttpResponse } from "@angular/common/http";
import { Observable } from "rxjs";
import { filter, map, share } from "rxjs/operators";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { Injectable } from '@angular/core';
import {
HttpClient, HttpEvent, HttpEventType, HttpResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { filter, map, share } from 'rxjs/operators';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
export interface UploadFile extends File {
description?:string;
customName?:string;
}
export interface UploadBlob extends Blob {
description?:string;
customName?:string;
@ -61,7 +62,7 @@ export interface MappedUploadResult {
@Injectable()
export class OpenProjectFileUploadService {
constructor(protected http:HttpClient,
protected halResource:HalResourceService) {
protected halResource:HalResourceService) {
}
/**
@ -75,11 +76,9 @@ export class OpenProjectFileUploadService {
public uploadAndMapResponse(url:string, files:UploadFile[], method = 'post') {
const { uploads, finished } = this.upload(url, files);
const mapped = finished
.then((result:HalResource[]) => result.map((el:HalResource) => {
return { response: el, uploadUrl: el.staticDownloadLocation.href };
})) as Promise<{ response:HalResource, uploadUrl:string }[]>;
.then((result:HalResource[]) => result.map((el:HalResource) => ({ response: el, uploadUrl: el.staticDownloadLocation.href }))) as Promise<{ response:HalResource, uploadUrl:string }[]>;
return { uploads: uploads, finished: mapped } as MappedUploadResult;
return { uploads, finished: mapped } as MappedUploadResult;
}
/**
@ -104,7 +103,7 @@ export class OpenProjectFileUploadService {
const formData = new FormData();
const metadata = {
description: file.description,
fileName: file.customName || file.name
fileName: file.customName || file.name,
};
// add the metadata object
@ -119,20 +118,20 @@ export class OpenProjectFileUploadService {
const observable = this
.http
.request<HalResource>(
method,
url,
{
body: formData,
// Observe the response, not the body
observe: 'events',
withCredentials: true,
responseType: responseType as any,
// Subscribe to progress events. subscribe() will fire multiple times!
reportProgress: true
}
)
method,
url,
{
body: formData,
// Observe the response, not the body
observe: 'events',
withCredentials: true,
responseType: responseType as any,
// Subscribe to progress events. subscribe() will fire multiple times!
reportProgress: true,
},
)
.pipe(
share()
share(),
);
return [file, observable] as UploadInProgress;
@ -144,14 +143,12 @@ export class OpenProjectFileUploadService {
* @param {UploadInProgress[]} uploads
*/
private whenFinished(uploads:UploadInProgress[]):Promise<HalResource[]> {
const promises = uploads.map(([_, observable]) => {
return observable
.pipe(
filter((evt) => evt.type === HttpEventType.Response),
map((evt:HttpResponse<HalResource>) => this.halResource.createHalResource(evt.body))
)
.toPromise();
});
const promises = uploads.map(([_, observable]) => observable
.pipe(
filter((evt) => evt.type === HttpEventType.Response),
map((evt:HttpResponse<HalResource>) => this.halResource.createHalResource(evt.body)),
)
.toPromise());
return Promise.all(promises);
}

@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HttpClient } from '@angular/common/http';
import { FormBuilder } from '@angular/forms';
import { FormsService } from './forms.service';
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { HttpClient } from "@angular/common/http";
import { FormBuilder } from "@angular/forms";
describe('FormsService', () => {
let service:FormsService;
@ -10,21 +10,21 @@ describe('FormsService', () => {
let httpTestingController:HttpTestingController;
const testFormUrl = 'http://op.com/form';
const formModel = {
"name": "Project 1",
"_links": {
"parent": {
"href": "/api/v3/projects/26",
"title": "Parent project",
"name": "Parent project"
name: 'Project 1',
_links: {
parent: {
href: '/api/v3/projects/26',
title: 'Parent project',
name: 'Parent project',
},
"users": [
users: [
{
"href": "/api/v3/users/26",
"title": "User 1",
"name": "User 1"
}
]
}
href: '/api/v3/users/26',
title: 'User 1',
name: 'User 1',
},
],
},
};
const formBuilder = new FormBuilder();
@ -76,57 +76,58 @@ describe('FormsService', () => {
// @ts-ignore
const formattedModel = service.formatModelToSubmit(formModel);
const expectedResult = {
"name": "Project 1",
"_links": {
"parent": {
"href": "/api/v3/projects/26",
name: 'Project 1',
_links: {
parent: {
href: '/api/v3/projects/26',
},
"users": [
users: [
{
"href": "/api/v3/users/26",
}
]
}
href: '/api/v3/users/26',
},
],
},
};
expect(formattedModel).toEqual(expectedResult);
});
it('should set the backend errors in the FormGroup', () => {
const form= formBuilder.group({
const form = formBuilder.group({
...formModel,
_links: formBuilder.group(formModel._links),
});
const backEndErrorResponse = {
error: {
"_type": "Error",
"errorIdentifier": "urn:openproject-org:api:v3:errors:MultipleErrors",
"message": "Multiple field constraints have been violated.",
"_embedded": {
"errors": [
_type: 'Error',
errorIdentifier: 'urn:openproject-org:api:v3:errors:MultipleErrors',
message: 'Multiple field constraints have been violated.',
_embedded: {
errors: [
{
"_type": "Error",
"errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
"message": "Name can't be blank.",
"_embedded": {
"details": {
"attribute": "name"
}
}
_type: 'Error',
errorIdentifier: 'urn:openproject-org:api:v3:errors:PropertyConstraintViolation',
message: "Name can't be blank.",
_embedded: {
details: {
attribute: 'name',
},
},
},
{
"_type": "Error",
"errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
"message": "Identifier can't be blank.",
"_embedded": {
"details": {
"attribute": "parent"
}
}
_type: 'Error',
errorIdentifier: 'urn:openproject-org:api:v3:errors:PropertyConstraintViolation',
message: "Identifier can't be blank.",
_embedded: {
details: {
attribute: 'parent',
},
},
},
]
}
}, status: 422
],
},
},
status: 422,
};
// @ts-ignore

@ -1,40 +1,39 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { FormGroup } from "@angular/forms";
import { catchError, map } from "rxjs/operators";
import { Observable } from "rxjs";
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { FormGroup } from '@angular/forms';
import { catchError, map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FormsService {
constructor(
private _httpClient:HttpClient,
) { }
submit$(form:FormGroup, resourceEndpoint:string, resourceId?:string, formHttpMethod?: 'post' | 'patch', formSchema?:IOPFormSchema):Observable<any> {
submit$(form:FormGroup, resourceEndpoint:string, resourceId?:string, formHttpMethod?:'post' | 'patch', formSchema?:IOPFormSchema):Observable<any> {
const modelToSubmit = this.formatModelToSubmit(form.getRawValue(), formSchema);
const httpMethod = resourceId ? 'patch' : (formHttpMethod || 'post');
const url = resourceId ? `${resourceEndpoint}/${resourceId}` : resourceEndpoint;
return this._httpClient
[httpMethod](
[httpMethod](
url,
modelToSubmit,
{
withCredentials: true,
responseType: 'json'
}
responseType: 'json',
},
)
.pipe(
catchError((error:HttpErrorResponse) => {
if (error.status == 422 ) {
if (error.status == 422) {
this.handleBackendFormValidationErrors(error, form);
}
throw error;
})
}),
);
}
@ -45,34 +44,14 @@ export class FormsService {
.post(
`${resourceEndpoint}/form`,
modelToSubmit,
{
withCredentials: true,
responseType: 'json'
}
)
.pipe(
map((response: HalSource) => this.getFormattedErrors(Object.values(response?._embedded?.validationErrors))),
map((formattedErrors: IFormattedValidationError[]) => this.setFormValidationErrors(formattedErrors, form)),
);
}
getFormBackendValidationError$(formValue: {[key:string]: any}, resourceEndpoint:string, limitValidationToKeys?:string | string[], formSchema?:IOPFormSchema) {
const modelToSubmit = this.formatModelToSubmit(formValue, formSchema);
return this._httpClient
.post(
resourceEndpoint,
modelToSubmit,
{
withCredentials: true,
responseType: 'json',
headers: {
'content-type': 'application/json; charset=utf-8'
}
}
},
)
.pipe(
map((response: HalSource) => this.getAllFormValidationErrors(response._embedded.validationErrors, limitValidationToKeys))
map((response:HalSource) => this.getFormattedErrors(Object.values(response?._embedded?.validationErrors))),
map((formattedErrors:IFormattedValidationError[]) => this.setFormValidationErrors(formattedErrors, form)),
);
}
@ -84,14 +63,14 @@ export class FormsService {
* in the shape of '{href:hrefValue}' in order to fit the backend expectations.
* */
private formatModelToSubmit(formModel:IOPFormModel, formSchema:IOPFormSchema = {}):IOPFormModel {
let {_links:linksModel, ...mainModel} = formModel;
let { _links: linksModel, ...mainModel } = formModel;
const resourcesModel = linksModel || Object.keys(formSchema)
.filter(formSchemaKey => !!formSchema[formSchemaKey]?.type && formSchema[formSchemaKey]?.location === '_links')
.filter((formSchemaKey) => !!formSchema[formSchemaKey]?.type && formSchema[formSchemaKey]?.location === '_links')
.reduce((result, formSchemaKey) => {
const {[formSchemaKey]:keyToRemove, ...mainModelWithoutResource} = mainModel;
const { [formSchemaKey]: keyToRemove, ...mainModelWithoutResource } = mainModel;
mainModel = mainModelWithoutResource;
return {...result, [formSchemaKey]: formModel[formSchemaKey]};
return { ...result, [formSchemaKey]: formModel[formSchemaKey] };
}, {});
const formattedResourcesModel = Object
@ -100,9 +79,9 @@ export class FormsService {
const resourceModel = resourcesModel[resourceKey];
// Form.payload resources have a HalLinkSource interface while
// API resource options have a IAllowedValue interface
const formattedResourceModel = Array.isArray(resourceModel) ?
resourceModel.map(resourceElement => ({ href: resourceElement?.href || resourceElement?._links?.self?.href || null })) :
{ href: resourceModel?.href || resourceModel?._links?.self?.href || null };
const formattedResourceModel = Array.isArray(resourceModel)
? resourceModel.map((resourceElement) => ({ href: resourceElement?.href || resourceElement?._links?.self?.href || null }))
: { href: resourceModel?.href || resourceModel?._links?.self?.href || null };
return {
...result,
@ -113,7 +92,7 @@ export class FormsService {
return {
...mainModel,
_links: formattedResourcesModel,
}
};
}
/** HAL resources formatting
@ -125,8 +104,8 @@ export class FormsService {
formatModelToEdit(formModel:IOPFormModel = {}):IOPFormModel {
const { _links: resourcesModel, _meta: metaModel, ...otherElements } = formModel;
const otherElementsModel = Object.keys(otherElements)
.filter(key => this.isValue(otherElements[key]))
.reduce((model, key) => ({...model, [key]:otherElements[key]}), {});
.filter((key) => this.isValue(otherElements[key]))
.reduce((model, key) => ({ ...model, [key]: otherElements[key] }), {});
const model = {
...otherElementsModel,
@ -138,8 +117,8 @@ export class FormsService {
}
private handleBackendFormValidationErrors(error:HttpErrorResponse, form:FormGroup):void {
const errors:IOPFormError[] = error?.error?._embedded?.errors ?
error?.error?._embedded?.errors : [error.error];
const errors:IOPFormError[] = error?.error?._embedded?.errors
? error?.error?._embedded?.errors : [error.error];
const formErrors = this.getFormattedErrors(errors);
this.setFormValidationErrors(formErrors, form);
@ -149,35 +128,32 @@ export class FormsService {
errors.forEach((err:any) => {
const formControl = form.get(err.key) || form.get('_links')?.get(err.key);
formControl?.setErrors({[err.key]: {message: err.message}});
formControl?.setErrors({ [err.key]: { message: err.message } });
});
}
private getAllFormValidationErrors(validationErrors:IOPValidationErrors, formControlKeys?:string | string[]): {[key:string]: {message:string}} {
private getAllFormValidationErrors(validationErrors:IOPValidationErrors, formControlKeys?:string | string[]):{ [key:string]:{ message:string } } {
const errors = Object.values(validationErrors);
const keysToValidate = Array.isArray(formControlKeys) ? formControlKeys : [formControlKeys];
const formErrors = this.getFormattedErrors(errors)
.filter(error => {
.filter((error) => {
if (!formControlKeys) {
return true;
} else {
return keysToValidate.includes(error.key);
}
return keysToValidate.includes(error.key);
})
.reduce((result, { key, message }) => {
return {
...result,
[key]: {message}
}
}, {})
.reduce((result, { key, message }) => ({
...result,
[key]: { message },
}), {});
return formErrors
return formErrors;
}
private getFormattedErrors(errors:IOPFormError[]):IFormattedValidationError[] {
const formattedErrors = errors.map(err => ({
const formattedErrors = errors.map((err) => ({
key: err._embedded.details.attribute,
message: err.message
message: err.message,
}));
return formattedErrors;
@ -188,13 +164,13 @@ export class FormsService {
const resource = resourcesModel[resourceKey];
// ng-select needs a 'name' in order to show the label
// We need to add it in case of the form payload (HalLinkSource)
const resourceModel = Array.isArray(resource) ?
resource.map(resourceElement => ({...resourceElement, name: resourceElement?.name || resourceElement?.title})) :
{...resource, name: resource?.name || resource?.title};
const resourceModel = Array.isArray(resource)
? resource.map((resourceElement) => ({ ...resourceElement, name: resourceElement?.name || resourceElement?.title }))
: { ...resource, name: resource?.name || resource?.title };
result = {
...result,
...this.isValue(resourceModel) && {[resourceKey]: resourceModel},
...this.isValue(resourceModel) && { [resourceKey]: resourceModel },
};
return result;

@ -1,5 +1,5 @@
interface IOPFormSettingsResource {
_type?:"Form";
_type?:'Form';
_embedded:IOPFormSettings;
_links?:{
self:IOPApiCall;
@ -17,7 +17,7 @@ interface IOPFormSettings {
}
interface IOPFormSchema {
_type?:"Schema";
_type?:'Schema';
_dependencies?:unknown[];
_attributeGroups?:IOPAttributeGroup[];
lockVersion?:number;
@ -85,11 +85,10 @@ interface IOPApiOption {
}
interface IOPAttributeGroup {
_type:
| "WorkPackageFormAttributeGroup"
| "WorkPackageFormChildrenQueryGroup"
| "WorkPackageFormRelationQueryGroup"
| unknown;
_type:| 'WorkPackageFormAttributeGroup'
| 'WorkPackageFormChildrenQueryGroup'
| 'WorkPackageFormRelationQueryGroup'
| unknown;
name:string;
attributes:string[];
}
@ -105,8 +104,8 @@ interface IOPAllowedValue {
}
type OPFieldType = 'String' | 'Integer' | 'Float' | 'Boolean' | 'Date' | 'DateTime' | 'Formattable' |
'Priority' | 'Status' | 'Type' | 'User' | 'Version' | 'TimeEntriesActivity' | 'Category' |
'CustomOption' | 'Project' | 'ProjectStatus' | 'Password';
'Priority' | 'Status' | 'Type' | 'User' | 'Version' | 'TimeEntriesActivity' | 'Category' |
'CustomOption' | 'Project' | 'ProjectStatus' | 'Password';
interface IOPFormError {
errorIdentifier:string;
@ -137,7 +136,3 @@ interface IOPFormErrors {
interface IOPValidationErrors {
[key:string]:IOPFormError;
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -40,9 +40,7 @@ export const globalSearchWorkPackagesSelectorEntry = 'global-search-work-package
<ng-container wp-isolated-query-space>
<global-search-work-packages></global-search-work-packages>
</ng-container>
`
`,
})
export class GlobalSearchWorkPackagesEntryComponent {
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,19 +26,21 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import {
AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2,
} from '@angular/core';
import { FocusHelperService } from 'core-app/shared/directives/focus/focus-helper';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { GlobalSearchService } from "core-app/core/global_search/services/global-search.service";
import { UrlParamsHelperService } from "core-app/features/work-packages/components/wp-query/url-params-helper";
import { WorkPackageTableConfigurationObject } from "core-app/features/work-packages/components/wp-table/wp-table-configuration";
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper';
import { WorkPackageTableConfigurationObject } from 'core-app/features/work-packages/components/wp-table/wp-table-configuration';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { WorkPackageViewFiltersService } from "core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service";
import { debounceTime, distinctUntilChanged, skip } from "rxjs/operators";
import { combineLatest } from "rxjs";
import { UntilDestroyedMixin } from "core-app/shared/helpers/angular/until-destroyed.mixin";
import { WorkPackageFiltersService } from "core-app/features/work-packages/components/filters/wp-filters/wp-filters.service";
import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
import { debounceTime, distinctUntilChanged, skip } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
import { WorkPackageFiltersService } from 'core-app/features/work-packages/components/filters/wp-filters/wp-filters.service';
export const globalSearchWorkPackagesSelector = 'global-search-work-packages';
@ -49,11 +51,12 @@ export const globalSearchWorkPackagesSelector = 'global-search-work-packages';
[queryProps]="queryProps"
[configuration]="tableConfiguration">
</wp-embedded-table>
`
`,
})
export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin implements OnInit, OnDestroy, AfterViewInit {
public queryProps:{ [key:string]:any };
public resultsHidden = false;
public tableConfiguration:WorkPackageTableConfigurationObject = {
@ -63,35 +66,35 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
inlineCreateEnabled: false,
withFilters: true,
showFilterButton: true,
filterButtonText: this.I18n.t('js.button_advanced_filter')
filterButtonText: this.I18n.t('js.button_advanced_filter'),
};
constructor(readonly FocusHelper:FocusHelperService,
readonly elementRef:ElementRef,
readonly renderer:Renderer2,
readonly I18n:I18nService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly wpTableFilters:WorkPackageViewFiltersService,
readonly querySpace:IsolatedQuerySpace,
readonly wpFilters:WorkPackageFiltersService,
readonly cdRef:ChangeDetectorRef,
private UrlParamsHelper:UrlParamsHelperService) {
readonly elementRef:ElementRef,
readonly renderer:Renderer2,
readonly I18n:I18nService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly wpTableFilters:WorkPackageViewFiltersService,
readonly querySpace:IsolatedQuerySpace,
readonly wpFilters:WorkPackageFiltersService,
readonly cdRef:ChangeDetectorRef,
private UrlParamsHelper:UrlParamsHelperService) {
super();
}
ngAfterViewInit() {
combineLatest([
this.globalSearchService.searchTerm$,
this.globalSearchService.projectScope$
this.globalSearchService.projectScope$,
])
.pipe(
skip(1),
distinctUntilChanged(),
debounceTime(10),
this.untilDestroyed()
this.untilDestroyed(),
)
.subscribe(([newSearchTerm, newProjectScope]) => {
.subscribe(([]) => {
this.wpFilters.visible = false;
this.setQueryProps();
});
@ -99,9 +102,9 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
this.globalSearchService
.resultsHidden$
.pipe(
this.untilDestroyed()
this.untilDestroyed(),
)
.subscribe((resultsHidden:boolean) => this.resultsHidden = resultsHidden);
.subscribe((resultsHidden:boolean) => (this.resultsHidden = resultsHidden));
}
ngOnInit():void {
@ -116,8 +119,8 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
filters.push({
search: {
operator: '**',
values: [this.globalSearchService.searchTerm]
}
values: [this.globalSearchService.searchTerm],
},
});
}
@ -125,8 +128,8 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
filters.push({
subprojectId: {
operator: '!*',
values: []
}
values: [],
},
});
columns = ['id', 'subject', 'type', 'status', 'updatedAt'];
}
@ -135,8 +138,8 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
filters.push({
subprojectId: {
operator: '*',
values: []
}
values: [],
},
});
}
@ -144,8 +147,7 @@ export class GlobalSearchWorkPackagesComponent extends UntilDestroyedMixin imple
'columns[]': columns,
filters: JSON.stringify(filters),
sortBy: JSON.stringify([['updatedAt', 'desc']]),
showHierarchies: false
showHierarchies: false,
};
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,32 +27,33 @@
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
NgZone,
OnDestroy,
ViewChild,
ViewEncapsulation,
NgZone, AfterViewInit
} from '@angular/core';
import { Observable, of } from "rxjs";
import { map, tap } from "rxjs/operators";
import { APIV3Service } from "../../apiv3/api-v3.service";
import { GlobalSearchService } from "core-app/core/global_search/services/global-search.service";
import { LinkHandling } from "core-app/shared/helpers/link-handling/link-handling";
import { Highlighting } from "core-app/features/work-packages/components/wp-fast-table/builders/highlighting/highlighting.functions";
import { DeviceService } from "core-app/core/browser/device.service";
import { ContainHelpers } from "core-app/shared/directives/focus/contain-helpers";
import { HalResourceNotificationService } from "core-app/features/hal/services/hal-resource-notification.service";
import { I18nService } from "core-app/core/i18n/i18n.service";
import { CurrentProjectService } from "core-app/core/current-project/current-project.service";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { OpAutocompleterComponent } from "core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component";
import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource";
import { HalResourceService } from "core-app/features/hal/services/hal-resource.service";
import { HalResource } from "core-app/features/hal/resources/hal-resource";
import { Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
import { isClickedWithModifier } from 'core-app/shared/helpers/link-handling/link-handling';
import { Highlighting } from 'core-app/features/work-packages/components/wp-fast-table/builders/highlighting/highlighting.functions';
import { DeviceService } from 'core-app/core/browser/device.service';
import { ContainHelpers } from 'core-app/shared/directives/focus/contain-helpers';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { OpAutocompleterComponent } from 'core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { APIV3Service } from '../../apiv3/api-v3.service';
export const globalSearchSelector = 'global-search-input';
@ -81,29 +82,31 @@ interface SearchOptionItem {
'./global-search.component.sass',
],
// Necessary because of ng-select
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
})
export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
@ViewChild('btn', { static: true }) btn:ElementRef;
@ViewChild(OpAutocompleterComponent, { static: true }) public ngSelectComponent:OpAutocompleterComponent;
public expanded = false;
public markable = false;
public isLoading = false;
getAutocompleterData = (query:string):Observable<any[]> => {
return this.autocompleteWorkPackages(query);
};
getAutocompleterData = (query:string):Observable<any[]> => this.autocompleteWorkPackages(query);
public autocompleterOptions = {
filters:[],
resource:'work_packages',
searchKey:'subjectOrId',
getOptionsFn: this.getAutocompleterData
filters: [],
resource: 'work_packages',
searchKey: 'subjectOrId',
getOptionsFn: this.getAutocompleterData,
};
/** Remember the current value */
public currentValue = '';
public isFocusedDirectly = (this.globalSearchService.searchTerm.length > 0);
/** Remember the item that best matches the query.
@ -113,27 +116,26 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
private unregisterGlobalListener:Function|undefined;
private isInitialized :boolean = false;
public text:{ [key:string]:string } = {
all_projects: this.I18n.t('js.global_search.all_projects'),
current_project: this.I18n.t('js.global_search.current_project'),
current_project_and_all_descendants: this.I18n.t('js.global_search.current_project_and_all_descendants'),
search: this.I18n.t('js.global_search.search'),
search_dots: this.I18n.t('js.global_search.search') + ' ...',
close_search: this.I18n.t('js.global_search.close_search')
search_dots: `${this.I18n.t('js.global_search.search')} ...`,
close_search: this.I18n.t('js.global_search.close_search'),
};
constructor(readonly elementRef:ElementRef,
readonly I18n:I18nService,
readonly apiV3Service:APIV3Service,
readonly PathHelperService:PathHelperService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly currentProjectService:CurrentProjectService,
readonly deviceService:DeviceService,
readonly cdRef:ChangeDetectorRef,
readonly halNotification:HalResourceNotificationService,
readonly ngZone:NgZone) {
readonly I18n:I18nService,
readonly apiV3Service:APIV3Service,
readonly pathHelperService:PathHelperService,
readonly halResourceService:HalResourceService,
readonly globalSearchService:GlobalSearchService,
readonly currentProjectService:CurrentProjectService,
readonly deviceService:DeviceService,
readonly cdRef:ChangeDetectorRef,
readonly halNotification:HalResourceNotificationService,
readonly ngZone:NgZone) {
}
ngAfterViewInit():void {
@ -175,7 +177,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
public redirectToWp(id:string, event:MouseEvent) {
event.stopImmediatePropagation();
if (LinkHandling.isClickedWithModifier(event)) {
if (isClickedWithModifier(event)) {
return true;
}
@ -185,7 +187,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
}
public wpPath(id:string) {
return this.PathHelperService.workPackagePath(id);
return this.pathHelperService.workPackagePath(id);
}
public search($event:any) {
@ -195,7 +197,6 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
// close menu when input field is empty
public openCloseMenu(searchedTerm:string) {
this.ngSelectComponent.ngSelectInstance.isOpen = (searchedTerm.trim().length > 0);
}
@ -209,7 +210,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
if (!this.deviceService.isMobile) {
this.expanded = (this.ngSelectComponent.ngSelectInstance.searchTerm !== null && this.ngSelectComponent.ngSelectInstance.searchTerm.length > 0);
this.ngSelectComponent.ngSelectInstance.isOpen = false;
this.selectedItem =null;
this.selectedItem = null;
this.toggleTopMenuClass();
}
}
@ -270,31 +271,27 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
// Hide highlighting of ng-option
this.markable = false;
const hashFreeQuery = this.queryWithoutHash(query);
this.isLoading = true
this.isLoading = true;
return this
.fetchSearchResults(hashFreeQuery, hashFreeQuery !== query)
.get()
.pipe(
map((collection) => {
return this.searchResultsToOptions(collection.elements, hashFreeQuery);
}),
map((collection) => this.searchResultsToOptions(collection.elements, hashFreeQuery)),
tap(() => {
this.isLoading = false;
this.setMarkedOption();})
this.setMarkedOption();
}),
);
}
// Remove ID marker # when searching for #<number>
private queryWithoutHash(query:string) {
if (query.match(/^#(\d+)/)) {
if (/^#(\d+)/.exec(query)) {
return query.substr(1);
} else {
return query;
}
return query;
}
private fetchSearchResults(query:string, idOnly:boolean) {
@ -306,7 +303,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
private searchResultsToOptions(results:WorkPackageResource[], query:string) {
const searchItems = results.map((wp) => {
const item = {
const item = {
id: wp.id!,
subject: wp.subject,
status: wp.status.name,
@ -314,7 +311,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
href: wp.href,
project: wp.project.name,
author: wp.author,
type: wp.type
type: wp.type,
} as SearchResultItem;
// If we have a direct hit, we choose it to be the selected element.
if (query === wp.id!.toString()) {
@ -347,9 +344,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
}
searchOptions.push('all_projects');
return searchOptions.map((suggestion:string) => {
return { projectScope: suggestion, text: this.text[suggestion] };
});
return searchOptions.map((suggestion:string) => ({ projectScope: suggestion, text: this.text[suggestion] }));
}
/*
@ -407,6 +402,8 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
this.submitNonEmptySearch();
break;
}
default: // Do nothing
break;
}
}
@ -415,9 +412,9 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
if (this.currentValue.length > 0) {
this.ngSelectComponent.ngSelectInstance.close();
// Work package results can update without page reload.
if (!forcePageLoad &&
this.globalSearchService.isAfterSearch() &&
this.globalSearchService.currentTab === 'work_packages') {
if (!forcePageLoad
&& this.globalSearchService.isAfterSearch()
&& this.globalSearchService.currentTab === 'work_packages') {
window.history
.replaceState({},
`${I18n.t('global_search.search')}: ${this.ngSelectComponent.ngSelectInstance.searchTerm}`,
@ -450,5 +447,3 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
jQuery('.op-app-header').toggleClass('op-app-header_search-open', this.expanded);
}
}

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -27,15 +27,15 @@
//++
import { NgModule } from '@angular/core';
import { OpenprojectWorkPackagesModule } from "core-app/features/work-packages/openproject-work-packages.module";
import { GlobalSearchInputComponent } from "core-app/core/global_search/input/global-search-input.component";
import { GlobalSearchWorkPackagesComponent } from "core-app/core/global_search/global-search-work-packages.component";
import { GlobalSearchTabsComponent } from "core-app/core/global_search/tabs/global-search-tabs.component";
import { GlobalSearchTitleComponent } from "core-app/core/global_search/title/global-search-title.component";
import { GlobalSearchService } from "core-app/core/global_search/services/global-search.service";
import { GlobalSearchWorkPackagesEntryComponent } from "core-app/core/global_search/global-search-work-packages-entry.component";
import { OpenprojectAutocompleterModule } from "core-app/shared/components/autocompleter/openproject-autocompleter.module";
import { OPSharedModule } from "core-app/shared/shared.module";
import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module';
import { GlobalSearchInputComponent } from 'core-app/core/global_search/input/global-search-input.component';
import { GlobalSearchWorkPackagesComponent } from 'core-app/core/global_search/global-search-work-packages.component';
import { GlobalSearchTabsComponent } from 'core-app/core/global_search/tabs/global-search-tabs.component';
import { GlobalSearchTitleComponent } from 'core-app/core/global_search/title/global-search-title.component';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
import { GlobalSearchWorkPackagesEntryComponent } from 'core-app/core/global_search/global-search-work-packages-entry.component';
import { OpenprojectAutocompleterModule } from 'core-app/shared/components/autocompleter/openproject-autocompleter.module';
import { OPSharedModule } from 'core-app/shared/shared.module';
@NgModule({
imports: [
@ -52,7 +52,6 @@ import { OPSharedModule } from "core-app/shared/shared.module";
GlobalSearchWorkPackagesComponent,
GlobalSearchTabsComponent,
GlobalSearchTitleComponent,
]
],
})
export class OpenprojectGlobalSearchModule { }

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,17 +26,17 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
/* jshint expr: true */
import { CurrentProjectService } from "core-app/core/current-project/current-project.service";
import { GlobalSearchService } from "core-app/core/global_search/services/global-search.service";
import { I18nService } from "core-app/core/i18n/i18n.service";
import { TestBed, waitForAsync } from "@angular/core/testing";
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { States } from "core-app/core/states/states.service";
import { APIV3Service } from "core-app/core/apiv3/api-v3.service";
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { States } from 'core-app/core/states/states.service';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
describe('Global search service', function() {
describe('Global search service', () => {
let service:GlobalSearchService;
let CurrentProject:CurrentProjectService;
let CurrentProjectSpy;
@ -51,13 +51,13 @@ describe('Global search service', function() {
APIV3Service,
CurrentProjectService,
GlobalSearchService,
]
],
})
.compileComponents()
.then(() => {
CurrentProject = TestBed.inject(CurrentProjectService);
service = TestBed.inject(GlobalSearchService);
});
.compileComponents()
.then(() => {
CurrentProject = TestBed.inject(CurrentProjectService);
service = TestBed.inject(GlobalSearchService);
});
}));
describe('outside a project', () => {

@ -1,4 +1,4 @@
//-- copyright
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
@ -26,37 +26,42 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
import { Injectable , Injector } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { I18nService } from "core-app/core/i18n/i18n.service";
import { CurrentProjectService } from "core-app/core/current-project/current-project.service";
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { PathHelperService } from "core-app/core/path-helper/path-helper.service";
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
@Injectable()
export class GlobalSearchService {
private _searchTerm = new BehaviorSubject<string>('');
public searchTerm$ = this._searchTerm.asObservable();
// Default selected tab is Work Packages
private _currentTab = new BehaviorSubject<any>('work_packages');
public currentTab$ = this._currentTab.asObservable();
// Default project scope is "this project and all subprojets"
private _projectScope = new BehaviorSubject<any>('');
public projectScope$ = this._projectScope.asObservable();
private _tabs = new BehaviorSubject<any>([]);
public tabs$ = this._tabs.asObservable();
// Sometimes we need to be able to hide the search results altogether, i.e. while expecting a full page reload.
private _resultsHidden = new BehaviorSubject<any>(false);
public resultsHidden$ = this._resultsHidden.asObservable();
constructor(protected I18n:I18nService,
protected injector:Injector,
protected PathHelper:PathHelperService,
protected currentProjectService:CurrentProjectService) {
protected injector:Injector,
protected PathHelper:PathHelperService,
protected currentProjectService:CurrentProjectService) {
this.initialize();
}
@ -81,10 +86,10 @@ export class GlobalSearchService {
}
}
private loadGonData():{available_search_types:string[],
search_term:string,
project_scope:string,
current_tab:string}|null {
private loadGonData():{ available_search_types:string[],
search_term:string,
project_scope:string,
current_tab:string }|null {
try {
return (window as any).gon.global_search;
} catch (e) {
@ -101,7 +106,7 @@ export class GlobalSearchService {
if (this.currentProjectService.path && this.projectScope !== 'all') {
searchPath = this.currentProjectService.path;
}
searchPath = searchPath + `/search?${this.searchQueryParams()}`;
searchPath += `/search?${this.searchQueryParams()}`;
return searchPath;
}

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

Loading…
Cancel
Save