Compare commits

...
45 Commits
Author SHA1 Message Date
deepusingh1304 7fdc76b48f Recheck request 2025-06-18 06:43:06 -04:00
deepusingh1304 4edd377cd3 Draft 2025-06-18 05:15:09 -04:00
Grzegorz Jaśkowski eda59a1de8 [ACS-8746] yellow warning color (#10883)
* [ACS-8746] yellow warning color

* [ACS-8746] move snackbar bg styling to app level

* [ACS-8746] missing comma
2025-06-13 12:50:11 -04:00
dependabot[bot] 6d5cda887d build(deps): bump Alfresco/alfresco-build-tools (#10934)
Bumps [Alfresco/alfresco-build-tools](https://github.com/alfresco/alfresco-build-tools) from 8.23.0 to 8.23.3.
- [Release notes](https://github.com/alfresco/alfresco-build-tools/releases)
- [Commits](https://github.com/alfresco/alfresco-build-tools/compare/f37c44cc085236178ebb8834eb634b0b4b550a40...c2278c8ec6744d3595478217e9736ebb47d501db)

---
updated-dependencies:
- dependency-name: Alfresco/alfresco-build-tools
  dependency-version: 8.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 10:27:43 +01:00
Wojciech Duda 3009fa6dec AAE-35848 Fix dynamic viewer container scaling (#10932)
* AAE-35848 Fix dynamic viewer container scaling

* AAE-35848 Add unit test

* AAE-35848 Fix custom viewer test
2025-06-13 09:57:11 +01:00
dependabot[bot] 441a6aab19 build(deps): bump Alfresco/alfresco-build-tools from 8.23.0 to 8.23.3 (#10935)
Bumps [Alfresco/alfresco-build-tools](https://github.com/alfresco/alfresco-build-tools) from 8.23.0 to 8.23.3.
- [Release notes](https://github.com/alfresco/alfresco-build-tools/releases)
- [Commits](https://github.com/alfresco/alfresco-build-tools/compare/f37c44cc085236178ebb8834eb634b0b4b550a40...c2278c8ec6744d3595478217e9736ebb47d501db)

---
updated-dependencies:
- dependency-name: Alfresco/alfresco-build-tools
  dependency-version: 8.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 09:56:19 +01:00
dependabot[bot] 47c1f92ed5 build(deps): bump github/codeql-action from 3.28.19 to 3.29.0 (#10936)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.19 to 3.29.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/fca7ace96b7d713c7035871441bd52efbe39e27e...ce28f5bb42b7a9f2c824e633a3f6ee835bab6858)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 09:39:48 +01:00
dependabot[bot] 402725d179 build(deps-dev): bump @storybook/manager-api from 8.6.2 to 8.6.14 (#10941)
Bumps [@storybook/manager-api](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/manager-api) from 8.6.2 to 8.6.14.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.6.14/code/lib/manager-api)

---
updated-dependencies:
- dependency-name: "@storybook/manager-api"
  dependency-version: 8.6.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 09:39:26 +01:00
Denys Vuika 8cfc8d32f2 AAE-35855 Security fixes (#10933)
* remove print-affected-libs action

* cleanup nx.json

* remove slack-group-area action
2025-06-12 17:04:00 -04:00
AleksanderSklorz cdc7553915 [MNT-24923] legal hold hold capabilities are not verified properly (#10930)
* [MNT-24923] Added possibility to filter capabilities by person id

* [MNT-24923] Documentation and unit tests
2025-06-12 08:21:49 -04:00
Alfresco BuildandCrowdin Bot 7a568baf72 New Crowdin translations by GitHub Action (#10928)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-12 08:18:48 -04:00
MichalKinasandMichaela f8a75d4af7 Viewer renderer stop loading when subsequent renderer throws error (#10931)
* Viewer renderer stop loading when subsequent renderer throws error

* Update lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.spec.ts

* CR fix

---------

Co-authored-by: Michaela <85624192+mkrbr@users.noreply.github.com>
2025-06-12 11:47:22 +02:00
Michaela 31c2cb2162 AAE-35728 Add aria label to viewer loading bars (#10927)
* Adds loading state accessibility

Improves accessibility by adding screen reader support to loading indicators.

This ensures that users with disabilities are informed when the document is loading, improving the overall user experience.

* Trigger Build

* Fixes accessibility issue on loading spinner

Removes the redundant `attr.` prefix from the `aria-labelledby` attribute in the loading spinner.
This resolves an accessibility issue where the spinner's label was not correctly associated, enhancing the user experience for assistive technologies.

* Trigger Build
2025-06-11 17:20:51 +02:00
Michaela 2c4764f89e Adds loading state accessibility (#10926)
Improves accessibility by adding screen reader support to loading indicators.

This ensures that users with disabilities are informed when the document is loading, improving the overall user experience.
2025-06-11 15:48:08 +02:00
MichaelaandEugenio Romano e2693f9304 AAE-35668 Sending empty string instead of null after clearing value from numeric field (#10925)
* Handles null values in number widget

Ensures the number widget correctly handles null, undefined, and empty string values, setting the field value to null in these cases.

This change prevents unexpected behavior when the user clears the input field, ensuring data consistency.

* Adds test for readonly number widget

Adds a test case to verify the behavior of the number widget when it's in readonly mode.

The test checks if the displayValue is correctly set using the decimalNumberPipe when the readOnly property is true.

* Refactors number widget tests

Updates number widget tests to use `overrideComponent` for providing mocked dependencies. This approach ensures proper isolation and avoids potential issues with shared state between tests.
Additionally, it adds a test case to verify the `displayValue` is correctly set using the mocked `DecimalNumberPipe`.

---------

Co-authored-by: Eugenio Romano <eromano@users.noreply.github.com>
2025-06-11 11:53:56 +02:00
MichalKinas 5d9acaec0d [AAE-35649] Viewer renderer wait until subsequent renderers will finish (#10924)
* [AAE-35649] Viewer renderer wait until particular renderers are done

* [AAE-35649] Add docs and unit test coverage

* [AAE-35649] Add missing space
2025-06-11 09:28:07 +02:00
dependabot[bot] 0c7bf62097 build(deps-dev): bump @typescript-eslint/typescript-estree (#10916)
Bumps [@typescript-eslint/typescript-estree](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-estree) from 7.1.1 to 8.33.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-estree/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.1/packages/typescript-estree)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/typescript-estree"
  dependency-version: 8.33.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 16:46:11 +01:00
dependabot[bot] 84116ad2bf build(deps): bump github/codeql-action from 3.28.18 to 3.28.19 (#10918)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff0a06e83cb2de871e5a09832bc6a81e7276941f...fca7ace96b7d713c7035871441bd52efbe39e27e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 16:26:19 +01:00
dependabot[bot] cd6c41305a build(deps): bump Alfresco/alfresco-build-tools from 8.22.1 to 8.23.0 (#10920)
Bumps [Alfresco/alfresco-build-tools](https://github.com/alfresco/alfresco-build-tools) from 8.22.1 to 8.23.0.
- [Release notes](https://github.com/alfresco/alfresco-build-tools/releases)
- [Commits](https://github.com/alfresco/alfresco-build-tools/compare/b3070acf733fdcfd2698065e99017d4969256e5b...f37c44cc085236178ebb8834eb634b0b4b550a40)

---
updated-dependencies:
- dependency-name: Alfresco/alfresco-build-tools
  dependency-version: 8.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 16:25:55 +01:00
Wojciech Duda 0c147513cc AAE-35521 Refactor AuthenticationService unit tests (#10922)
* AAE-35521 Refactor AuthenticationService

* AAE-35521 Remove last ajax call

* AAE-35521 Add missing test
2025-06-09 16:07:22 +01:00
dependabot[bot] ac14541c2e build(deps): bump crowdin/github-action from 2.7.0 to 2.7.1 (#10919)
Bumps [crowdin/github-action](https://github.com/crowdin/github-action) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/crowdin/github-action/releases)
- [Commits](https://github.com/crowdin/github-action/compare/b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2...f214c8723025f41fc55b2ad26e67b60b80b1885d)

---
updated-dependencies:
- dependency-name: crowdin/github-action
  dependency-version: 2.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 10:55:32 +01:00
Wojciech Dudaandtomson 920b7d3585 AAE-34641 Fix ViewerRenderComponent hanging when in a mat-tab (#10913)
* AAE-34641 Fix ViewerRenderComponent hanging when in a mat-tab

Co-authored-by: tomson <tomasz.nastaly@hyland.com>

* AAE-34641 Fix pdf scaling

* AAE-34641 Remove RenderType refactoring

---------

Co-authored-by: tomson <tomasz.nastaly@hyland.com>
2025-06-06 14:21:10 +02:00
Domenico Sibilio 643b5f723a Revert "AAE-34641 Refactor viewer-render loading (#10868)" (#10912)
This reverts commit 413fce8cb7.
2025-06-04 18:44:53 +02:00
AleksanderSklorz a6ab5fcbdd [ACS-9696] fix failing upgrade rxjs from 7.8.1 to 7.8.2 (#10911)
* [ACS-9696] Upgrade rxjs version to fix faling upgrade PR in ACA

* [ACS-9696] Update package-lock.json

* [ACS-9696] Fixed unit tests
2025-06-03 13:54:30 +02:00
Wojciech Duda 35970b4278 AAE-21284 Add migration guide for Angular 17 to 18 (#10910)
* AAE-21284 Add migration guide for Angular 17 to 18

* AAE-21284 Unify doc file names

* AAE-21284 Redact
2025-06-03 13:39:51 +02:00
dependabot[bot] 6f8762f46e build(deps): bump Alfresco/alfresco-build-tools (#10908)
Bumps [Alfresco/alfresco-build-tools](https://github.com/alfresco/alfresco-build-tools) from 8.21.1 to 8.22.1.
- [Release notes](https://github.com/alfresco/alfresco-build-tools/releases)
- [Commits](https://github.com/alfresco/alfresco-build-tools/compare/95f68dc050fba62b3331d9b47bc659526cdfb343...b3070acf733fdcfd2698065e99017d4969256e5b)

---
updated-dependencies:
- dependency-name: Alfresco/alfresco-build-tools
  dependency-version: 8.22.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 16:04:49 +01:00
MichalKinas 379fbbb508 [MNT-25043] Skip adding json as content type for FormData body requests (#10903) 2025-06-02 15:49:58 +02:00
Alfresco BuildandCrowdin Bot 342f165b41 New Crowdin translations by GitHub Action (#10887)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-02 12:46:53 +01:00
dependabot[bot] ecba77dfa9 bump @angular-eslint/template-parser from 17.0.1 to 19.6.0 (#10907)
Bumps [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) from 17.0.1 to 19.6.0.
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/template-parser/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/template-parser)

---
updated-dependencies:
- dependency-name: "@angular-eslint/template-parser"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 12:13:32 +01:00
dependabot[bot] a5b55c5861 bump postcss from 8.4.41 to 8.5.4 (#10906)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.41 to 8.5.4.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.41...8.5.4)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 12:10:59 +01:00
dependabot[bot] 2347ca68c4 build(deps): bump Alfresco/alfresco-build-tools from 8.21.1 to 8.22.1 (#10909)
Bumps [Alfresco/alfresco-build-tools](https://github.com/alfresco/alfresco-build-tools) from 8.21.1 to 8.22.1.
- [Release notes](https://github.com/alfresco/alfresco-build-tools/releases)
- [Commits](https://github.com/alfresco/alfresco-build-tools/compare/95f68dc050fba62b3331d9b47bc659526cdfb343...b3070acf733fdcfd2698065e99017d4969256e5b)

---
updated-dependencies:
- dependency-name: Alfresco/alfresco-build-tools
  dependency-version: 8.22.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 12:10:40 +01:00
AleksanderSklorz cdb65bf204 [ACS-9377] remove the join button for records management library for user s without permissions for that library (#10901)
* [ACS-9377] Added function for file plans to get file plan roles

* [ACS-9377] Types and documentation

* [ACS-9377] Unit tests

* [ACS-9377] Fixed sonar cloud issue
2025-06-02 10:15:19 +02:00
Enrico Hilgendorf 8cb7a1719f AAE-34656 Hide all counters in Workspace which do not provide value (#10896)
* Hide all counters in Workspace which do not provide value

* Fixed a copy & paste issue
2025-05-30 07:44:00 +02:00
dominikiwanekhyland 1971980216 [ACS-9619] Issues with trimming file's name in viewer (#10858) 2025-05-29 15:22:58 +02:00
Amedeo Lepore 467a19f3ec AAE-34959 Fix withCredentials can not be set by app.config.json (#10897)
* AAE-34959 Run security options loader when alfresco-api-v2-loader.serfvice is initialized because on content app the app-config.loader is run after AlfrescoApiLoaderService.init and the callback function that should setDefaultSecurityOption is not executed

* AAE-34959 Provide SecurityOptionsLoaderService in root to fix No Provider error
2025-05-29 10:24:17 +02:00
Mykyta Maliarchuk d1e48f9c33 [ACS-9247] Extend library column context (#10894)
* [ACS-9247] extend library column context

* [ACS-9247] cr fix
2025-05-29 10:11:07 +02:00
Soumyajit Chakraborty 79163cbae0 AAE-34482 Fixing label and alignment issues in forms (#10898)
* AAE-34482 fixing label and alignment issues in forms

* AAE-34482 fixing margin issues

* AAE-34482 adding span inside mat-label

* AAE-34482 fixing units

* AAE-34482 removing unwanted unit

* AAE-34482 fixing unit

* AAE-34482 fixing native element class in unit tests
2025-05-28 23:20:17 +05:30
dependabot[bot]andEugenio Romano 5bbb5c5716 build(deps-dev): bump stylelint from 16.8.2 to 16.19.1 (#10874)
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.8.2 to 16.19.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.8.2...16.19.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.19.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eugenio Romano <eromano@users.noreply.github.com>
2025-05-26 14:02:58 -04:00
dependabot[bot] 0018cd7f8f build(deps): bump formidable from 3.5.2 to 3.5.4 (#10895)
Bumps [formidable](https://github.com/node-formidable/formidable) from 3.5.2 to 3.5.4.
- [Release notes](https://github.com/node-formidable/formidable/releases)
- [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/node-formidable/formidable/commits)

---
updated-dependencies:
- dependency-name: formidable
  dependency-version: 3.5.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 15:35:14 +02:00
Michael Couné 06921783bf AAE-33907 Adds input for 'Open next task' checkbox (#10823)
* [AAE-33907] Adds input for 'Open next task' checkbox

* Adds input for 'Open next task' checkbox

Adds an input to the task screen component to determine whether the "Open next task" checkbox is checked by default.

Also, adds an output that emits an event when the state of the "Open next task" checkbox changes.

* [AAE-33907] added condition for isNextTaskCheckboxChecked

* [AAE-33907] added showNextTaskCheckbox property and moved condition

* Adds next task checkbox functionality.

* Adds support for next task navigation

* Enhances screen cloud component testing

* Makes openNextTask optional for complete task

* removed tests

* Cleans up unnecessary blank lines in spec file

* fixed unit test
2025-05-23 11:50:38 +02:00
Enrico Hilgendorf 1462560e6e AAE-35057 Change position of adf-cloud-form-content-card-fullscreen to relative (#10888) 2025-05-23 07:00:18 +02:00
dependabot[bot] ecbfae5648 build(deps): bump @babel/runtime and @angular-devkit/build-angular (#10885)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) to 7.26.10 and updates ancestor dependency [@angular-devkit/build-angular](https://github.com/angular/angular-cli). These dependencies need to be updated together.


Updates `@babel/runtime` from 7.25.0 to 7.26.10
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

Updates `@angular-devkit/build-angular` from 18.2.14 to 18.2.19
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.14...18.2.19)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.26.10
  dependency-type: indirect
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 18.2.19
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-22 12:43:00 +01:00
Tomasz Nastaly 413fce8cb7 AAE-34641 Refactor viewer-render loading (#10868) 2025-05-22 10:07:26 +02:00
Bartosz Sekula f2fa458fe5 AAE-33909 Ensure onProcessFinish event triggers when invoked from onFormLoaded event (#10867)
* AAE-33909 Ensure onProcessFinish event triggers when invoked from onFormLoaded event

* clean code

* fix unit tests

* update outcomes buttons

* update
2025-05-21 16:01:05 +02:00
Swetha Balasubramaniam e37ef279e4 AAE-32986 Add custom-form-widget process to simpleapp (#10881)
* Update simpleapp.zip

* Update

* Update date-fns-utils.spec.ts

* Changes
2025-05-21 08:20:58 +01:00
131 changed files with 2943 additions and 6015 deletions
@@ -1 +0,0 @@
dist
@@ -1,21 +0,0 @@
name: "print affected libs"
description: "Retrive the affected libs based on the based and head input"
inputs:
base:
description: 'the base sha'
required: true
type: string
default: main
head:
description: 'the head sha'
required: true
type: string
default: HEAD
output:
affected:
description: 'the affected libs of the repo'
affected_is_empty:
description: 'true or false accondingly'
runs:
using: "node16"
main: 'dist/index.js'
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-521
View File
@@ -1,521 +0,0 @@
{
"name": "print-affected-libs",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "print-affected-libs",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.36.1"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/github": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^2.2.0",
"@octokit/core": "^5.0.1",
"@octokit/plugin-paginate-rest": "^9.0.0",
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
"integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz",
"integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^8.3.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/request": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
"integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^9.0.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@vercel/ncc": {
"version": "0.36.1",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz",
"integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==",
"dev": true,
"bin": {
"ncc": "dist/ncc/cli.js"
}
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
"license": "Apache-2.0"
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"license": "ISC"
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/github": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
"requires": {
"@actions/http-client": "^2.2.0",
"@octokit/core": "^5.0.1",
"@octokit/plugin-paginate-rest": "^9.0.0",
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
}
},
"@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"requires": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="
},
"@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="
},
"@octokit/core": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
"integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
"requires": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"requires": {
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz",
"integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==",
"requires": {
"@octokit/request": "^8.3.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g=="
},
"@octokit/plugin-paginate-rest": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"requires": {
"@octokit/types": "^12.6.0"
},
"dependencies": {
"@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
},
"@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"requires": {
"@octokit/openapi-types": "^20.0.0"
}
}
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"requires": {
"@octokit/types": "^12.6.0"
},
"dependencies": {
"@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
},
"@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"requires": {
"@octokit/openapi-types": "^20.0.0"
}
}
}
},
"@octokit/request": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
"integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
"requires": {
"@octokit/endpoint": "^9.0.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"requires": {
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
},
"@vercel/ncc": {
"version": "0.36.1",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz",
"integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==",
"dev": true
},
"before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"requires": {
"wrappy": "1"
}
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"undici": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"requires": {
"@fastify/busboy": "^2.0.0"
}
},
"universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}
@@ -1,20 +0,0 @@
{
"name": "print-affected-libs",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "ncc build src/index.js -o dist --source-map"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.36.1"
}
}
@@ -1,32 +0,0 @@
const core = require('@actions/core');
const exec = require('@actions/exec');
async function run() {
const base = core.getInput('base', {required: false }) || '5288597580';
core.setOutput('affected_is_empty', 'true');
let affected = '';
let myError = '';
const options = {};
options.listeners = {
stdout: (data) => {
affected += data.toString()
},
stderr: (data) => {
myError += data.toString();
}
};
await exec.exec(`npx nx print-affected --target=build --select=tasks.target.project --base=${base} --head=${head}`, [], options)
const affectedTrimmed = affected.trim();
if (affectedTrimmed !== '') {
core.setOutput('affected_is_empty', 'false');
}
core.notice(`Retriving affected libs: ${affectedTrimmed}`);
core.setOutput('affected', affectedTrimmed);
}
run();
+1 -1
View File
@@ -26,7 +26,7 @@ runs:
cache-dependency-path: package-lock.json
- name: get latest tag sha
id: tag-sha
uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@c2278c8ec6744d3595478217e9736ebb47d501db # v8.23.3
# CACHE
- name: Node Modules cache
id: node-modules-cache
@@ -1,49 +0,0 @@
name: Identify the slack group
description: 'Identify the slack group area based on the affected'
inputs:
affected:
description: 'The name of the affected lib'
required: true
type: string
outputs:
groups:
description: "the slack groups"
value: ${{ steps.group.outputs.result }}
runs:
using: "composite"
steps:
- name: Append group
id: group
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
affectedLib: ${{ inputs.affected }}
with:
script: |
const affectedLib = process.env.affectedLib;
core.info(`Input ${affectedLib}`);
const slackGroups = new Set();
const AlfrescoQAGroup = '@alfresco-testing-qa';
const HxpQAGroup = '@hxp-studio-qa-group';
const libs = affectedLib.split(' ')
if (libs.includes('content-services') || libs.includes('process-services')) {
slackGroups.add(AlfrescoQAGroup);
}
if (libs.includes('process-services-cloud')) {
slackGroups.add(HxpQAGroup);
}
if (libs.includes('core')) {
slackGroups.add(AlfrescoQAGroup);
slackGroups.add(HxpQAGroup);
}
const result = Array.from(slackGroups.values()).join(' ');
core.info(`Result ${result}`);
return result;
-8
View File
@@ -50,10 +50,6 @@ updates:
directory: "/.github/actions/npm-check-bundle"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github/actions/print-affected-libs"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github/actions/set-npm-tag"
schedule:
@@ -62,10 +58,6 @@ updates:
directory: "/.github/actions/setup"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github/actions/slack-group-area"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github/actions/upload-node-modules-and-artifacts"
schedule:
+3 -3
View File
@@ -30,7 +30,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
# Override language selection by uncommenting this and choosing your languages
with:
languages: javascript
@@ -39,7 +39,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -53,4 +53,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Pull translations from Crowdin
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
uses: crowdin/github-action@f214c8723025f41fc55b2ad26e67b60b80b1885d # v2.7.1
with:
upload_sources: false
download_translations: true
+8 -25
View File
@@ -84,10 +84,10 @@ jobs:
fetch-depth: 0
- name: Get branch name
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/get-branch-name@c2278c8ec6744d3595478217e9736ebb47d501db # v8.23.3
- name: Save commit message
uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@95f68dc050fba62b3331d9b47bc659526cdfb343 # v8.21.1
uses: Alfresco/alfresco-build-tools/.github/actions/get-commit-message@c2278c8ec6744d3595478217e9736ebb47d501db # v8.23.3
- name: ci:force flag parser
shell: bash
@@ -270,27 +270,10 @@ jobs:
name: Final Results
needs: [check-if-pr-is-approved, pre-checks, setup, unit-tests, lint, build-libs]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: identify-slack-group
id: groups
if: ${{ github.event_name == 'schedule' }}
uses: ./.github/actions/slack-group-area
with:
affected: ${{ steps.e2e-result.outputs.result }}
- uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
name: Nofify QA failure
if: ${{ github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
channel-id: 'C016SMNNL8L' #guild-channel
slack-message: "🔴 Warning: The daily ADF cronjob failed\nWorkflow run : <https://github.com/Alfresco/alfresco-ng2-components/actions/runs/${{ github.run_id }}| here>\nDetails: ${{ steps.e2e-result.outputs.result }}\nArea: ${{ steps.groups.outputs.groups}}"
- name: workflow failure
- name: Check job execution status
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
}}
run: exit 1
if: ${{ contains(needs.*.result, 'failure') }}
- name: workflow canceled
run: exit 1
if: ${{ contains(needs.*.result, 'cancelled') }}
- name: workflow success
run: exit 0
if: ${{ contains(needs.*.result, 'success') }}
+8 -10
View File
@@ -215,7 +215,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Push Source Files to Crowdin
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
uses: crowdin/github-action@f214c8723025f41fc55b2ad26e67b60b80b1885d # v2.7.1
with:
upload_sources: true
upload_sources_args: --delete-obsolete
@@ -228,12 +228,10 @@ jobs:
name: Final Results
needs: [release-storybook, release-npm, npm-check-bundle]
steps:
- uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
name: Nofify FE eng-guild-front-end workflow failed
if: ${{ contains(toJson(needs.*.result), 'failure') }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
channel-id: 'C016SMNNL8L' #eng-guild-front-end
slack-message: "🔴 Warning: The release workflow of alfresco-ng2-components pipe failed\n Author username: ${{ github.event.pull_request.user.login }}\n Workflow run <https://github.com/Alfresco/alfresco-ng2-components/actions/runs/${{ github.run_id }}| here>\n>"
- name: Check job execution status
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
}}
run: exit 1
+102
View File
@@ -0,0 +1,102 @@
# Migrating from Angular 17 to Angular 18
This guide describes how to migrate ADF from Angular version 17 to version 18. Follow these steps to ensure a smooth transition.
## 1. Update changed dependencies
Update the following packages in your `package.json`:
### dependencies
```json
{
"@angular/animations": "18.2.13",
"@angular/cdk": "18.2.14",
"@angular/common": "18.2.13",
"@angular/compiler": "18.2.13",
"@angular/core": "18.2.13",
"@angular/forms": "18.2.13",
"@angular/material": "18.2.14",
"@angular/material-date-fns-adapter": "18.2.14",
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
"@apollo/client": "3.13.1",
"@mat-datetimepicker/core": "14.0.0",
"apollo-angular": "10.0.3",
"minimatch-browser": "1.0.0",
"node-fetch": "^3.3.2",
"zone.js": "0.14.10"
}
```
### devDependencies
```json
{
"@angular-devkit/architect": "0.1802.13",
"@angular-devkit/build-angular": "18.2.14",
"@angular-devkit/core": "18.2.13",
"@angular-devkit/schematics": "18.2.13",
"@angular/compiler-cli": "18.2.13",
"@nx/angular": "19.2.0",
"@nx/js": "18.3.5",
"@nx/workspace": "18.3.5",
"@storybook/angular": "8.4.7",
"ng-packagr": "18.2.1",
"npm-run-all": "^4.1.5",
"postcss": "8.4.41",
"postcss-sass": "^0.5.0",
"tsconfig-paths": "^4.1.1",
"typescript": "5.5.4",
"webpack-cli": "^5.1.4"
}
```
## 2. Material mixins update
Angular Material 18 introduced Material 3 mixins as new default. As we are still using Material 2, we need to ensure that we are still using the Material 2 version.
In order to use Material 2 mixins, you need to add a `m2-` prefix to the mixins in your stylesheets. For example:
```scss
mat.define-typography-config()
mat.get-color-from-palette()
```
should be changed to:
```scss
mat.m2-define-typography-config()
mat.m2-get-color-from-palette()
```
## 3. Fixing CI/CD issues
After running the migration script, you may need to manually fix some issues. Run common build targets to identify any problems.
```bash
npx nx run-many --target=build --skip-nx-cache
```
If blocked by linting issues, disable the linting rules in the affected files. You can re-enable them in the next step.
### Lint jobs
After getting the build to work, run the lint jobs. Re-enable previously disabled linting rules and fix any issues that arise.
```bash
npx nx run-many --target=lint --skip-nx-cache
```
### Unit tests
Run the unit tests and fix any problems that arise.
```bash
npx nx run-many --target=test --skip-nx-cache
```
## 4. Review Angular 18 breaking changes
Angular 18 is a minor update with no major breaking changes for most projects. However, always review the [Angular 18 changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) and [official update guide](https://angular.dev/update-guide?v=17.0-18.0).
@@ -245,16 +245,17 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the component. Below is an example that shows how to use the `adf-viewer-extension`
to handle 3D data files:
to handle 3D data files. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<adf-alfresco-viewer [nodeId]="nodeId">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['obj','3ds']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension">
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" let-markAsLoaded="markAsLoaded">
<threed-viewer
[urlFile]="urlFileContent"
[extension]="extension">
[extension]="extension"
(contentLoaded)="markAsLoaded()">
</threed-viewer>
</ng-template>
</adf-viewer-extension>
@@ -270,17 +271,19 @@ You need to keep all instances of `adf-viewer-extension` inside `viewerExtension
<adf-alfresco-viewer [nodeId]="nodeId">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-extension>
@@ -206,22 +206,24 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
#### Code extension mechanism
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the [Viewer render component](viewer.component.md). In order to do that first you need to define a template containing at least one `adf-viewer-extension`:
the [Viewer render component](viewer.component.md). In order to do that first you need to define a template containing at least one `adf-viewer-extension`. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-render-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-render-extension>
+10 -7
View File
@@ -215,16 +215,17 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the [Viewer component](viewer.component.md). Below is an example that shows how to use the `adf-viewer-extension`
to handle 3D data files:
to handle 3D data files. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<adf-viewer [urlFile]="urlFile">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['obj','3ds']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension">
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" let-markAsLoaded="markAsLoaded">
<threed-viewer
[urlFile]="urlFileContent"
[extension]="extension">
[extension]="extension"
(contentLoaded)="markAsLoaded()">
</threed-viewer>
</ng-template>
</adf-viewer-extension>
@@ -239,17 +240,19 @@ You need to keep all instances of `adf-viewer-extension` inside `viewerExtension
<adf-viewer [urlFile]="urlFile">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded" >
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-extension>
Binary file not shown.
+1 -1
View File
@@ -25,7 +25,7 @@
"ejs": "^3.1.9",
"license-checker": "^25.0.1",
"node-fetch": "^2.7.0",
"rxjs": "^6.6.6",
"rxjs": "7.8.2",
"shelljs": "^0.8.3",
"spdx-license-list": "^5.0.0"
},
@@ -19,6 +19,7 @@ import { AlfrescoApiConfig } from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { AppConfigService, AppConfigValues, StorageService } from '@alfresco/adf-core';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { SecurityOptionsLoaderService } from '../security-options-loader/security-options-loader.service';
/**
* Create a factory to resolve an api service instance
@@ -37,11 +38,12 @@ export class AlfrescoApiLoaderService {
constructor(
private readonly appConfig: AppConfigService,
private readonly apiService: AlfrescoApiService,
private readonly securityOptionsLoaderService: SecurityOptionsLoaderService,
private storageService: StorageService
) {}
async init(): Promise<any> {
await this.appConfig.load();
await this.appConfig.load(this.securityOptionsLoaderService.load);
return this.initAngularAlfrescoApi();
}
@@ -94,7 +94,7 @@ describe('DownloadZipDialogComponent', () => {
spyOn(downloadZipService, 'createDownload').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -112,7 +112,7 @@ describe('DownloadZipDialogComponent', () => {
const createDownloadSpy = spyOn(downloadZipService, 'createDownload').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -125,7 +125,7 @@ describe('DownloadZipDialogComponent', () => {
spyOn(downloadZipService, 'createDownload').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -140,7 +140,7 @@ describe('DownloadZipDialogComponent', () => {
spyOn(downloadZipService, 'createDownload').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -92,4 +92,19 @@ describe('LibraryRoleColumnComponent', () => {
fixture.detectChanges();
expect(value).toBe('LIBRARY.ROLE.NONE');
});
it('should take role from obj when node entry role is not provided', () => {
component.context = {
row: {
node: { entry: {} },
obj: { role: 'SiteManager' }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.ROLE.MANAGER');
});
});
@@ -17,7 +17,7 @@
import { ChangeDetectionStrategy, Component, DestroyRef, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Site, SiteEntry } from '@alfresco/js-api';
import { Site } from '@alfresco/js-api';
import { ShareDataRow } from '../../data/share-data-row.model';
import { NodesApiService } from '../../../common/services/nodes-api.service';
import { CommonModule } from '@angular/common';
@@ -64,26 +64,23 @@ export class LibraryRoleColumnComponent implements OnInit {
}
protected updateValue() {
const node: SiteEntry = this.context.row.node;
if (node?.entry) {
const role: string = node.entry.role;
switch (role) {
case Site.RoleEnum.SiteManager:
this.displayText$.next('LIBRARY.ROLE.MANAGER');
break;
case Site.RoleEnum.SiteCollaborator:
this.displayText$.next('LIBRARY.ROLE.COLLABORATOR');
break;
case Site.RoleEnum.SiteContributor:
this.displayText$.next('LIBRARY.ROLE.CONTRIBUTOR');
break;
case Site.RoleEnum.SiteConsumer:
this.displayText$.next('LIBRARY.ROLE.CONSUMER');
break;
default:
this.displayText$.next('LIBRARY.ROLE.NONE');
break;
}
const role = this.context.row.node?.entry.role ?? this.context.row.obj.role;
switch (role) {
case Site.RoleEnum.SiteManager:
this.displayText$.next('LIBRARY.ROLE.MANAGER');
break;
case Site.RoleEnum.SiteCollaborator:
this.displayText$.next('LIBRARY.ROLE.COLLABORATOR');
break;
case Site.RoleEnum.SiteContributor:
this.displayText$.next('LIBRARY.ROLE.CONTRIBUTOR');
break;
case Site.RoleEnum.SiteConsumer:
this.displayText$.next('LIBRARY.ROLE.CONSUMER');
break;
default:
this.displayText$.next('LIBRARY.ROLE.NONE');
break;
}
}
}
@@ -16,9 +16,54 @@
*/
import { LibraryStatusColumnComponent } from './library-status-column.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { Site } from '@alfresco/js-api';
describe('LibraryStatusColumnComponent', () => {
it('should be defined', () => {
expect(LibraryStatusColumnComponent).toBeDefined();
});
let fixture: ComponentFixture<LibraryStatusColumnComponent>;
let component: LibraryStatusColumnComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentTestingModule, LibraryStatusColumnComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
fixture = TestBed.createComponent(LibraryStatusColumnComponent);
component = fixture.componentInstance;
});
it('should be defined', () => {
expect(LibraryStatusColumnComponent).toBeDefined();
});
it('should take default visibility from node entry', () => {
component.context = {
row: {
node: { entry: { visibility: Site.VisibilityEnum.PUBLIC } }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.VISIBILITY.PUBLIC');
});
it('should take visibility from obj when node entry visibility is not provided', () => {
component.context = {
row: {
node: { entry: {} },
obj: { visibility: Site.VisibilityEnum.PUBLIC }
}
};
let value = '';
component.displayText$.subscribe((val) => (value = val));
fixture.detectChanges();
expect(value).toBe('LIBRARY.VISIBILITY.PUBLIC');
});
});
@@ -18,7 +18,7 @@
import { Component, DestroyRef, inject, Input, OnInit } from '@angular/core';
import { NodesApiService } from '../../../common/services/nodes-api.service';
import { BehaviorSubject } from 'rxjs';
import { Site, SiteEntry } from '@alfresco/js-api';
import { Site } from '@alfresco/js-api';
import { ShareDataRow } from '../../data/share-data-row.model';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@@ -62,24 +62,21 @@ export class LibraryStatusColumnComponent implements OnInit {
}
protected updateValue() {
const node: SiteEntry = this.context.row.node;
if (node?.entry) {
const visibility: string = node.entry.visibility;
const visibility = this.context.row.node?.entry.visibility ?? this.context.row.obj.visibility;
switch (visibility) {
case Site.VisibilityEnum.PUBLIC:
this.displayText$.next('LIBRARY.VISIBILITY.PUBLIC');
break;
case Site.VisibilityEnum.PRIVATE:
this.displayText$.next('LIBRARY.VISIBILITY.PRIVATE');
break;
case Site.VisibilityEnum.MODERATED:
this.displayText$.next('LIBRARY.VISIBILITY.MODERATED');
break;
default:
this.displayText$.next('UNKNOWN');
break;
}
switch (visibility) {
case Site.VisibilityEnum.PUBLIC:
this.displayText$.next('LIBRARY.VISIBILITY.PUBLIC');
break;
case Site.VisibilityEnum.PRIVATE:
this.displayText$.next('LIBRARY.VISIBILITY.PRIVATE');
break;
case Site.VisibilityEnum.MODERATED:
this.displayText$.next('LIBRARY.VISIBILITY.MODERATED');
break;
default:
this.displayText$.next('UNKNOWN');
break;
}
}
}
@@ -96,7 +96,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -115,7 +115,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -136,7 +136,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -157,7 +157,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -175,7 +175,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -196,7 +196,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -212,7 +212,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -236,7 +236,7 @@ describe('FolderActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callFake(
() =>
new Observable<any>((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -69,6 +69,7 @@ export abstract class InfiniteScrollDatasource<T> extends DataSource<T> {
reset(): void {
this.isLoading$.next(true);
this.dataStream.next([]);
this.getNextBatch({ skipCount: 0, maxItems: this.batchSize })
.pipe(take(1))
.subscribe((firstBatch) => {
@@ -0,0 +1,73 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SecurityOptionsLoaderService } from './security-options-loader.service';
import { AppConfigService, AppConfigValues } from '@alfresco/adf-core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
describe('SecurityOptionsLoaderService', () => {
let service: SecurityOptionsLoaderService;
let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>;
let adfHttpClientSpy: jasmine.SpyObj<AdfHttpClient>;
beforeEach(() => {
appConfigServiceSpy = jasmine.createSpyObj('AppConfigService', ['get']);
adfHttpClientSpy = jasmine.createSpyObj('AdfHttpClient', ['setDefaultSecurityOption']);
service = new SecurityOptionsLoaderService(appConfigServiceSpy, adfHttpClientSpy);
});
it('should set withCredentials when value is true', () => {
appConfigServiceSpy.get.and.callFake((key: string): any => {
if (key === AppConfigValues.AUTH_WITH_CREDENTIALS) {
return true;
}
});
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).toHaveBeenCalledWith({ withCredentials: true });
});
it('should set withCredentials when value is false', () => {
appConfigServiceSpy.get.and.callFake((key: string): any => {
if (key === AppConfigValues.AUTH_WITH_CREDENTIALS) {
return false;
}
});
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).toHaveBeenCalledWith({ withCredentials: false });
});
it('should not call setDefaultSecurityOption when value is undefined', () => {
appConfigServiceSpy.get.and.returnValue(undefined);
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).not.toHaveBeenCalled();
});
it('should not call setDefaultSecurityOption when value is null', () => {
appConfigServiceSpy.get.and.returnValue(null);
service.load();
expect(adfHttpClientSpy.setDefaultSecurityOption).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,34 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AppConfigService, AppConfigValues } from '@alfresco/adf-core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SecurityOptionsLoaderService {
constructor(private appConfigService: AppConfigService, private adfHttpClient: AdfHttpClient) {}
load = () => {
const withCredentials = this.appConfigService.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS);
if (withCredentials !== undefined && withCredentials !== null) {
this.adfHttpClient.setDefaultSecurityOption({ withCredentials });
}
};
}
@@ -45,7 +45,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) {
return this.authService.addTokenToHeader(req.url, req.headers).pipe(
mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
const headerWithContentType = this.appendJsonContentType(headersWithBearer, req.body);
const kcReq = req.clone({ headers: headerWithContentType });
return next.handle(kcReq).pipe(catchError((error) => observableThrowError(error)));
})
@@ -55,7 +55,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
return next.handle(req).pipe(catchError((error) => observableThrowError(error)));
}
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
private appendJsonContentType(headers: HttpHeaders, reqBody: any): HttpHeaders {
// prevent adding any content type, to properly handle formData with boundary browser generated value,
// as adding any Content-Type its going to break the upload functionality
@@ -63,7 +63,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
return headers.delete('Content-Type');
}
if (!headers.get('Content-Type')) {
if (!headers.get('Content-Type') && !(reqBody instanceof FormData)) {
return headers.set('Content-Type', 'application/json;charset=UTF-8');
}
+1 -1
View File
@@ -24,7 +24,7 @@
"angular-oauth2-oidc": "17.0.2",
"angular-oauth2-oidc-jwks": "17.0.2",
"date-fns": "^2.30.0",
"rxjs": "7.8.1"
"rxjs": "7.8.2"
},
"peerDependencies": {
"@angular/animations": ">=16.0.0",
@@ -20,20 +20,30 @@ import { BasicAlfrescoAuthService } from './basic-alfresco-auth.service';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { ProcessAuth } from './process-auth';
import { ContentAuth } from './content-auth';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { take } from 'rxjs/operators';
describe('BasicAlfrescoAuthService', () => {
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let processAuth: ProcessAuth;
let contentAuth: ContentAuth;
let appConfig: AppConfigService;
let appConfigSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [BasicAlfrescoAuthService]
providers: [BasicAlfrescoAuthService, provideHttpClient()]
});
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
spyOn(TestBed.inject(ProcessAuth), 'getToken').and.returnValue('Mock Process Auth ticket');
spyOn(TestBed.inject(ContentAuth), 'getToken').and.returnValue('Mock Content Auth ticket');
const appConfigSpy = spyOn(TestBed.inject(AppConfigService), 'get');
processAuth = TestBed.inject(ProcessAuth);
spyOn(processAuth, 'getToken').and.returnValue('Mock Process Auth ticket');
contentAuth = TestBed.inject(ContentAuth);
spyOn(contentAuth, 'getToken').and.returnValue('Mock Content Auth ticket');
appConfig = TestBed.inject(AppConfigService);
appConfigSpy = spyOn(appConfig, 'get');
appConfigSpy.withArgs(AppConfigValues.CONTEXTROOTBPM).and.returnValue('activiti-app');
appConfigSpy.withArgs(AppConfigValues.CONTEXTROOTECM).and.returnValue('alfresco');
});
@@ -59,4 +69,159 @@ describe('BasicAlfrescoAuthService', () => {
const ticket = basicAlfrescoAuthService.getTicketEcmBase64('http://www.example.com/activiti-app/alfresco/mock-api-url');
expect(ticket).toEqual('Basic Mock Process Auth ticket');
});
describe('login', () => {
let contentAuthSpy: jasmine.Spy;
let processAuthSpy: jasmine.Spy;
beforeEach(() => {
contentAuthSpy = spyOn(contentAuth, 'login');
processAuthSpy = spyOn(processAuth, 'login');
});
it('should return both ECM and BPM tickets after the login', async () => {
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ALL');
const mockContentTicket = { ticket: 'mock-content-ticket' };
const mockProcessTicket = { ticket: 'mock-process-ticket' };
const onLogin = firstValueFrom(basicAlfrescoAuthService.onLogin.pipe(take(1)));
contentAuthSpy.and.returnValue(Promise.resolve(mockContentTicket));
processAuthSpy.and.returnValue(Promise.resolve(mockProcessTicket));
const result = await firstValueFrom(basicAlfrescoAuthService.login('username', 'password'));
expect(result).toEqual({
type: 'ALL',
ticket: [mockContentTicket, mockProcessTicket]
});
expect(contentAuth.login).toHaveBeenCalledWith('username', 'password');
expect(processAuth.login).toHaveBeenCalledWith('username', 'password');
expect(await onLogin).toBe('success');
});
it('should return login fail on ECM login failure', async () => {
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ECM');
const mockError = 'ECM login failed';
contentAuthSpy.and.returnValue(Promise.reject(mockError));
const onError = firstValueFrom(basicAlfrescoAuthService.onError.pipe(take(1)));
await expectAsync(firstValueFrom(basicAlfrescoAuthService.login('username', 'password'))).toBeRejectedWith(mockError);
expect(contentAuth.login).toHaveBeenCalledWith('username', 'password');
expect(processAuth.login).not.toHaveBeenCalled();
expect(await onError).toBe('ECM login failed');
});
it('should return login fail on BPM login failure', async () => {
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('BPM');
const mockError = 'BPM login failed';
processAuthSpy.and.returnValue(Promise.reject(mockError));
const onError = firstValueFrom(basicAlfrescoAuthService.onError.pipe(take(1)));
await expectAsync(firstValueFrom(basicAlfrescoAuthService.login('username', 'password'))).toBeRejectedWith(mockError);
expect(contentAuth.login).not.toHaveBeenCalled();
expect(processAuth.login).toHaveBeenCalledWith('username', 'password');
expect(await onError).toBe('BPM login failed');
});
});
it('isBpmLoggedIn should return value from processAuth', () => {
spyOn(processAuth, 'isLoggedIn').and.returnValue(true);
const result = basicAlfrescoAuthService.isBpmLoggedIn();
expect(result).toBeTrue();
expect(processAuth.isLoggedIn).toHaveBeenCalled();
});
it('isEcmLoggedIn should return value from contentAuth', () => {
spyOn(contentAuth, 'isLoggedIn').and.returnValue(true);
const result = basicAlfrescoAuthService.isEcmLoggedIn();
expect(result).toBeTrue();
expect(contentAuth.isLoggedIn).toHaveBeenCalled();
});
describe('isLoggedIn', () => {
let contentAuthSpy: jasmine.Spy;
let processAuthSpy: jasmine.Spy;
beforeEach(() => {
contentAuthSpy = spyOn(contentAuth, 'isLoggedIn');
processAuthSpy = spyOn(processAuth, 'isLoggedIn');
});
it('should default to false when no provider is set', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(false);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue(null);
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeFalse();
expect(contentAuth.isLoggedIn).not.toHaveBeenCalled();
expect(processAuth.isLoggedIn).not.toHaveBeenCalled();
});
describe('BPM provider', () => {
it('should return processAuth value', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(true);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('BPM');
processAuthSpy.and.returnValue(true);
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeTrue();
expect(processAuth.isLoggedIn).toHaveBeenCalled();
});
});
describe('ECM provider', () => {
it('should return true when kerberos enabled', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(true);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ECM');
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeTrue();
});
it('should return contentAuth value when kerberos disabled', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(false);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ECM');
contentAuthSpy.and.returnValue(true);
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeTrue();
expect(contentAuth.isLoggedIn).toHaveBeenCalled();
});
});
describe('ALL provider', () => {
it('should return true when kerberos enabled', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(true);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ALL');
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeTrue();
expect(processAuth.isLoggedIn).not.toHaveBeenCalled();
expect(contentAuth.isLoggedIn).not.toHaveBeenCalled();
});
it('should return true if both contentAuth or processAuth is logged in when kerberos disabled', () => {
appConfigSpy.withArgs(AppConfigValues.AUTH_WITH_CREDENTIALS, false).and.returnValue(false);
appConfigSpy.withArgs(AppConfigValues.PROVIDERS).and.returnValue('ALL');
contentAuthSpy.and.returnValue(true);
processAuthSpy.and.returnValue(true);
const result = basicAlfrescoAuthService.isLoggedIn();
expect(result).toBeTrue();
expect(contentAuth.isLoggedIn).toHaveBeenCalled();
expect(processAuth.isLoggedIn).toHaveBeenCalled();
});
});
});
});
@@ -20,7 +20,7 @@ import { OidcAuthenticationService } from './oidc-authentication.service';
import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { AppConfigService, AuthService } from '@alfresco/adf-core';
import { AUTH_MODULE_CONFIG } from './auth-config';
import { of } from 'rxjs';
import { firstValueFrom, of, take, throwError } from 'rxjs';
interface MockAppConfigOAuth2 {
oauth2: {
@@ -28,59 +28,99 @@ interface MockAppConfigOAuth2 {
};
}
class MockAppConfigService {
config: MockAppConfigOAuth2 = {
const mockAppConfigService = {
config: {
oauth2: {
logoutParameters: ['client_id', 'returnTo', 'response_type']
}
};
} as MockAppConfigOAuth2,
setConfig(newConfig: { logoutParameters: Array<string> }) {
this.config.oauth2 = newConfig;
}
},
get(key: string, defaultValue?: { logoutParameters: Array<string> }) {
if (key === 'oauth2') {
return this.config.oauth2;
}
if (key === 'providers') {
return 'mock-providers';
}
return defaultValue;
}
}
};
class MockOAuthService {
clientId = 'testClientId';
redirectUri = 'testRedirectUri';
logOut = jasmine.createSpy();
}
const mockOAuthService = {
clientId: 'testClientId',
redirectUri: 'testRedirectUri',
logOut: jasmine.createSpy(),
hasValidAccessToken: jasmine.createSpy(),
hasValidIdToken: jasmine.createSpy()
};
const mockAuthService = {
baseAuthLogin: jasmine.createSpy()
};
describe('OidcAuthenticationService', () => {
let service: OidcAuthenticationService;
let oauthService: OAuthService;
let appConfig: AppConfigService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
OidcAuthenticationService,
{ provide: AppConfigService, useClass: MockAppConfigService },
{ provide: OAuthService, useClass: MockOAuthService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: OAuthService, useValue: mockOAuthService },
{ provide: OAuthStorage, useValue: {} },
{ provide: AUTH_MODULE_CONFIG, useValue: {} },
{ provide: AuthService, useValue: {} }
{ provide: AuthService, useValue: mockAuthService }
]
});
service = TestBed.inject(OidcAuthenticationService);
oauthService = TestBed.inject(OAuthService);
appConfig = TestBed.inject(AppConfigService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('login', () => {
it('should call AuthService with credentials', async () => {
const appConfigSpy = spyOn(mockAppConfigService, 'get').and.callThrough();
const mockTokenResponse = { access_token: 'mock-token' };
mockAuthService.baseAuthLogin.and.returnValue(of(mockTokenResponse));
const onLogin = firstValueFrom(service.onLogin.pipe(take(1)));
const response = await firstValueFrom(service.login('username', 'password'));
expect(mockAuthService.baseAuthLogin).toHaveBeenCalledWith('username', 'password');
expect(await onLogin).toEqual(mockTokenResponse);
expect(response).toEqual({
type: 'mock-providers',
ticket: mockTokenResponse
});
expect(appConfigSpy).toHaveBeenCalledWith('providers');
});
it('should handle login errors', async () => {
const mockError = 'Login failed';
mockAuthService.baseAuthLogin.and.returnValue(throwError(() => mockError));
const onError = firstValueFrom(service.onError.pipe(take(1)));
await expectAsync(firstValueFrom(service.login('username', 'password'))).toBeRejectedWith(mockError);
expect(await onError).toEqual(mockError);
});
});
describe('logout', () => {
let mockAppConfigService: MockAppConfigService;
let appConfigService: AppConfigService;
beforeEach(() => {
mockAppConfigService = TestBed.inject(AppConfigService) as any;
appConfigService = TestBed.inject(AppConfigService) as any;
});
it('should handle logout with default parameters', () => {
@@ -93,7 +133,7 @@ describe('OidcAuthenticationService', () => {
});
it('should handle logout with additional parameter redirect_uri', () => {
mockAppConfigService.setConfig({
appConfigService['setConfig']({
logoutParameters: ['client_id', 'returnTo', 'redirect_uri', 'response_type']
});
@@ -108,7 +148,7 @@ describe('OidcAuthenticationService', () => {
});
it('should handle logout with an empty configuration object', () => {
mockAppConfigService.setConfig({ logoutParameters: [] });
appConfigService['setConfig']({ logoutParameters: [] });
service.logout();
@@ -116,7 +156,7 @@ describe('OidcAuthenticationService', () => {
});
it('should ignore undefined parameters', () => {
mockAppConfigService.setConfig({
appConfigService['setConfig']({
logoutParameters: ['client_id', 'unknown_param']
});
service.logout();
@@ -127,6 +167,82 @@ describe('OidcAuthenticationService', () => {
});
});
describe('loggedIn', () => {
it('should return true if has valid tokens', () => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
mockOAuthService.hasValidIdToken.and.returnValue(true);
expect(service.isLoggedIn()).toBeTrue();
});
it('should return false if does not have valid access token', () => {
mockOAuthService.hasValidAccessToken.and.returnValue(false);
mockOAuthService.hasValidIdToken.and.returnValue(true);
expect(service.isLoggedIn()).toBeFalse();
});
it('should return false if does not have valid id token', () => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
mockOAuthService.hasValidIdToken.and.returnValue(false);
expect(service.isLoggedIn()).toBeFalse();
});
});
describe('isEcmLoggedIn', () => {
beforeEach(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
mockOAuthService.hasValidIdToken.and.returnValue(true);
});
it('should return true if is ECM provider', () => {
spyOn(appConfig, 'get').and.returnValue('ECM');
expect(service.isEcmLoggedIn()).toBeTrue();
});
it('should return true if is all provider', () => {
spyOn(appConfig, 'get').and.returnValue('ALL');
expect(service.isEcmLoggedIn()).toBeTrue();
});
it('should return false if is not ECM provider', () => {
spyOn(appConfig, 'get').and.returnValue('BPM');
expect(service.isEcmLoggedIn()).toBeFalse();
});
it('should return false if provider is not defined', () => {
spyOn(appConfig, 'get').and.returnValue(undefined);
expect(service.isEcmLoggedIn()).toBeFalse();
});
});
describe('isBpmLoggedIn', () => {
beforeEach(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
mockOAuthService.hasValidIdToken.and.returnValue(true);
});
it('should return true if is BPM provider', () => {
spyOn(appConfig, 'get').and.returnValue('BPM');
expect(service.isBpmLoggedIn()).toBeTrue();
});
it('should return true if is all provider', () => {
spyOn(appConfig, 'get').and.returnValue('ALL');
expect(service.isBpmLoggedIn()).toBeTrue();
});
it('should return false if is not BPM provider', () => {
spyOn(appConfig, 'get').and.returnValue('ECM');
expect(service.isBpmLoggedIn()).toBeFalse();
});
it('should return false if provider is not defined', () => {
spyOn(appConfig, 'get').and.returnValue(undefined);
expect(service.isBpmLoggedIn()).toBeFalse();
});
});
});
describe('OidcAuthenticationService shouldPerformSsoLogin', () => {
@@ -136,8 +252,8 @@ describe('OidcAuthenticationService shouldPerformSsoLogin', () => {
TestBed.configureTestingModule({
providers: [
OidcAuthenticationService,
{ provide: AppConfigService, useClass: MockAppConfigService },
{ provide: OAuthService, useClass: MockOAuthService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: OAuthService, useValue: mockOAuthService },
{ provide: OAuthStorage, useValue: {} },
{ provide: AUTH_MODULE_CONFIG, useValue: {} },
{ provide: AuthService, useValue: {} },
@@ -15,35 +15,37 @@
* limitations under the License.
*/
import { fakeAsync, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { AuthenticationService } from './authentication.service';
import { CookieService } from '../../common/services/cookie.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { AuthModule } from '../oidc/auth.module';
import { HttpClientModule, HttpHeaders } from '@angular/common/http';
import { HttpHeaders, provideHttpClient } from '@angular/common/http';
import { CookieServiceMock } from '../../mock';
import { AppConfigServiceMock } from '../../common';
import { OidcAuthenticationService } from '../oidc/oidc-authentication.service';
import { OAuthEvent } from 'angular-oauth2-oidc';
import { Subject } from 'rxjs';
import { firstValueFrom, of, Subject, throwError } from 'rxjs';
import { RedirectAuthService } from '../oidc/redirect-auth.service';
import { Injector } from '@angular/core';
import { NoopTranslateModule } from '../../testing/noop-translate.module';
import { ContentAuth, ProcessAuth } from '../public-api';
declare let jasmine: any;
// eslint-disable-next-line
xdescribe('AuthenticationService', () => {
describe('AuthenticationService', () => {
let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let appConfigService: AppConfigService;
let cookie: CookieService;
let oidcAuthenticationService: OidcAuthenticationService;
let headers: HttpHeaders;
let processAuth: ProcessAuth;
let contentAuth: ContentAuth;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoopTranslateModule, AuthModule.forRoot({ useHash: true }), HttpClientModule],
imports: [NoopTranslateModule, AuthModule.forRoot({ useHash: true })],
providers: [
{
provide: CookieService,
@@ -52,7 +54,8 @@ xdescribe('AuthenticationService', () => {
{
provide: AppConfigService,
useClass: AppConfigServiceMock
}
},
provideHttpClient()
]
});
@@ -61,11 +64,16 @@ xdescribe('AuthenticationService', () => {
authService = TestBed.inject(AuthenticationService);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
spyOn(oidcAuthenticationService, 'logout').and.returnValue(of());
cookie = TestBed.inject(CookieService);
processAuth = TestBed.inject(ProcessAuth);
spyOn(processAuth, 'login').and.returnValue(Promise.resolve());
contentAuth = TestBed.inject(ContentAuth);
spyOn(contentAuth, 'login').and.returnValue(Promise.resolve());
cookie.clear();
jasmine.Ajax.install();
appConfigService = TestBed.inject(AppConfigService);
appConfigService.config.pagination = {
supportedPageSizes: []
@@ -74,7 +82,6 @@ xdescribe('AuthenticationService', () => {
afterEach(() => {
cookie.clear();
jasmine.Ajax.uninstall();
});
describe('kerberos', () => {
@@ -147,56 +154,18 @@ xdescribe('AuthenticationService', () => {
expect(authService.isEcmLoggedIn()).toBeFalsy();
});
it('[ECM] should return an ECM ticket after the login done', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
expect(authService.getToken()).toEqual('fake-post-ticket');
expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
done();
});
it('[ECM] should login in the ECM if no provider are defined calling the login', async () => {
contentAuth.login = jasmine.createSpy().and.returnValue(Promise.resolve(fakeECMLoginResponse.ticket));
const loginResponse = await firstValueFrom(basicAlfrescoAuthService.login('fake-username', 'fake-password'));
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
expect(contentAuth.login).toHaveBeenCalled();
expect(loginResponse).toEqual(fakeECMLoginResponse);
});
it('[ECM] should login in the ECM if no provider are defined calling the login', fakeAsync(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe((loginResponse) => {
expect(loginResponse).toEqual(fakeECMLoginResponse);
disposableLogin.unsubscribe();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
}));
it('[ECM] should return a ticket undefined after logout', fakeAsync(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getToken()).toBe(null);
expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
disposableLogout.unsubscribe();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 204
});
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
}));
it('[ECM] should get token when provider is ECM', () => {
spyOn(basicAlfrescoAuthService, 'getToken').and.returnValue('fake-ecm-token');
expect(basicAlfrescoAuthService.getToken()).toEqual('fake-ecm-token');
});
it('[ECM] should return false if the user is not logged in', () => {
expect(authService.isLoggedIn()).toBe(false);
@@ -261,55 +230,12 @@ xdescribe('AuthenticationService', () => {
expect(authService.isBpmLoggedIn()).toBeFalsy();
});
it('[BPM] should return an BPM ticket after the login done', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
// cspell: disable-next
expect(authService.getToken()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
done();
});
it('[BPM] should return an error when the logout return error', async () => {
oidcAuthenticationService.logout = jasmine.createSpy().and.returnValue(throwError(() => 'logout'));
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json'
});
});
it('[BPM] should return a ticket undefined after logout', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogout.unsubscribe();
disposableLogin.unsubscribe();
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200
});
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200
});
});
it('[BPM] should return an error when the logout return error', (done) => {
authService.logout().subscribe(
() => {},
(err: any) => {
expect(err).toBeDefined();
expect(authService.getToken()).toBe(null);
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403
await firstValueFrom(authService.logout()).catch((err) => {
expect(err).toBeDefined();
expect(authService.getToken()).toBe(null);
});
});
@@ -350,59 +276,23 @@ xdescribe('AuthenticationService', () => {
appConfigService.load();
});
it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', false).subscribe(() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull();
disposableLogin.unsubscribe();
done();
});
it('[ECM] should save the remember me cookie as a session cookie after successful login', async () => {
await firstValueFrom(basicAlfrescoAuthService.login('fake-username', 'fake-password', false));
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
expect(cookie.getItem('ALFRESCO_REMEMBER_ME')).toBe('1');
});
it('[ECM] should save the remember me cookie as a persistent cookie after successful login', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', true).subscribe(() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull();
disposableLogin.unsubscribe();
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
it('[ECM] should save the remember me cookie as a persistent cookie after successful login', async () => {
await firstValueFrom(basicAlfrescoAuthService.login('fake-username', 'fake-password', true));
expect(cookie.getItem('ALFRESCO_REMEMBER_ME')).not.toBeUndefined();
expect(JSON.parse(cookie.getItem('ALFRESCO_REMEMBER_ME')).expiration).not.toBeNull();
});
it('[ECM] should not save the remember me cookie after failed login', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).toBeUndefined();
disposableLogin.unsubscribe();
done();
}
);
it('[ECM] should not save the remember me cookie after failed login', async () => {
contentAuth.login = jasmine.createSpy().and.returnValue(Promise.reject(new Error('Login failed')));
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403,
contentType: 'application/json',
responseText: JSON.stringify({
error: {
errorKey: 'Login failed',
statusCode: 403,
briefSummary: '05150009 Login failed',
stackTrace: 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.',
descriptionURL: 'https://api-explorer.alfresco.com'
}
})
await firstValueFrom(basicAlfrescoAuthService.login('fake-username', 'fake-password')).catch(() => {
expect(cookie.getItem('ALFRESCO_REMEMBER_ME')).toBeNull();
});
});
});
@@ -413,99 +303,6 @@ xdescribe('AuthenticationService', () => {
appConfigService.load();
});
it('[ALL] should return both ECM and BPM tickets after the login done', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
expect(basicAlfrescoAuthService.getTicketEcm()).toEqual('fake-post-ticket');
// cspell: disable-next
expect(basicAlfrescoAuthService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true);
expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
done();
});
jasmine.Ajax.requests.at(0).respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
jasmine.Ajax.requests.at(1).respondWith({
status: 200
});
});
it('[ALL] should return login fail if only ECM call fail', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false, 'isLoggedIn');
expect(authService.getToken()).toBe(null, 'getTicketEcm');
// cspell: disable-next
expect(authService.getToken()).toBe(null, 'getTicketBpm');
expect(authService.isEcmLoggedIn()).toBe(false, 'isEcmLoggedIn');
disposableLogin.unsubscribe();
done();
}
);
jasmine.Ajax.requests.at(0).respondWith({
status: 403
});
jasmine.Ajax.requests.at(1).respondWith({
status: 200
});
});
it('[ALL] should return login fail if only BPM call fail', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getToken()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
done();
}
);
jasmine.Ajax.requests.at(0).respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
jasmine.Ajax.requests.at(1).respondWith({
status: 403
});
});
it('[ALL] should return ticket undefined when the credentials are wrong', (done) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getToken()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
done();
}
);
jasmine.Ajax.requests.at(0).respondWith({
status: 403
});
jasmine.Ajax.requests.at(1).respondWith({
status: 403
});
});
it('[ALL] should return isECMProvider false', () => {
expect(authService.isECMProvider()).toBe(false);
});
@@ -123,7 +123,7 @@ export abstract class BaseAuthenticationService implements AuthenticationService
*/
handleError(error: any): Observable<any> {
this.onError.next(error || 'Server error');
return throwError(error || 'Server error');
return throwError(() => error || 'Server error');
}
isOauth(): boolean {
@@ -78,7 +78,6 @@ describe('DateFnsUtils', () => {
it('should parse alternative ISO datetime', () => {
const result = DateFnsUtils.parseDate('1982-03-13T10:00:000Z', `yyyy-MM-dd'T'HH:mm:sssXXX`);
expect(result.toISOString()).toBe('1982-03-13T10:00:00.000Z');
});
@@ -18,6 +18,7 @@
import { Directive, EventEmitter, Input, Output } from '@angular/core';
import { ThemePalette } from '@angular/material/core';
import { FormFieldModel, FormFieldValidator, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets';
import { isOutcomeButtonVisible } from './helpers/buttons-visibility';
@Directive({
standalone: true
@@ -103,10 +104,6 @@ export abstract class FormBaseComponent {
*/
formStyle: string = '';
get hasVisibleOutcomes(): boolean {
return this.form?.outcomes?.some((outcome) => this.isOutcomeButtonVisible(outcome, this.form.readOnly));
}
get form(): FormModel {
return this._form;
}
@@ -169,22 +166,7 @@ export abstract class FormBaseComponent {
}
isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean {
if (outcome?.name) {
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return this.showCompleteButton;
}
if (isFormReadOnly) {
return outcome.isSelected;
}
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return this.showSaveButton;
}
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
return false;
}
return true;
}
return false;
return isOutcomeButtonVisible(outcome, { isFormReadOnly, showCompleteButton: this.showCompleteButton, showSaveButton: this.showSaveButton });
}
/**
@@ -73,6 +73,7 @@ export class FormFieldComponent implements OnInit, OnDestroy {
if (w.adf === undefined) {
w.adf = {};
}
const originalField = this.getField();
if (originalField) {
const customTemplate = this.field.form.customFieldTemplates[originalField.type];
@@ -2,11 +2,13 @@
[ngClass]="{ 'adf-readonly-form': formDefinition.readOnly }">
<div *ngIf="formDefinition.hasTabs()">
<div *ngIf="hasTabs()" class="alfresco-tabs-widget">
<mat-tab-group>
<mat-tab *ngFor="let tab of visibleTabs()" [label]="tab.title | translate ">
<ng-container class="adf-form-tab-content">
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: tab.fields }" />
</ng-container>
<mat-tab-group [preserveContent]="true">
<mat-tab *ngFor="let tab of visibleTabs()" [label]="tab.title | translate ">
<ng-template matTabContent>
<div class="adf-form-tab-content">
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: tab.fields }" />
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
@@ -40,7 +42,8 @@
<section class="adf-grid-list-column-view" *ngIf="currentRootElement?.isExpanded">
<div class="adf-grid-list-single-column"
*ngFor="let column of currentRootElement?.columns"
[style.width.%]="getColumnWidth(currentRootElement)">
[style.width.%]="getColumnWidth(currentRootElement)"
>
<ng-container *ngFor="let field of column?.fields">
<ng-container *ngIf="field.type === 'section'; else formField">
<adf-form-section [field]="field"/>
@@ -61,9 +64,11 @@
</div>
</ng-template>
</div>
<div *ngIf="currentRootElement.type === 'dynamic-table'" class="adf-container-widget">
<adf-form-field [field]="currentRootElement" />
</div>
<div class="adf-container-widget"
*ngIf="currentRootElement.type === 'readonly' && currentRootElement.field.params.field.type === 'dynamic-table'">
<adf-form-field [field]="currentRootElement.field"/>
@@ -16,6 +16,10 @@
.alfresco-tabs-widget {
width: 100%;
.adf-form-tab-content {
margin-top: 1em;
}
.adf-form-tab-group {
width: 100%;
}
@@ -0,0 +1,45 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormOutcomeModel } from '../widgets';
interface IsOutcomeButtonVisibleProps {
isFormReadOnly: boolean;
showCompleteButton: boolean;
showSaveButton: boolean;
}
export const isOutcomeButtonVisible = (outcome: FormOutcomeModel, props: IsOutcomeButtonVisibleProps): boolean => {
const { isFormReadOnly, showCompleteButton, showSaveButton } = props;
if (outcome?.name) {
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return showCompleteButton;
}
if (isFormReadOnly) {
return outcome.isSelected;
}
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return showSaveButton;
}
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
return false;
}
return true;
}
return false;
};
@@ -10,9 +10,9 @@
>
</div>
<div>
<mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true">
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"
>{{field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span></label
<mat-form-field class="adf-amount-widget__input" [hideRequiredMarker]="true" [floatLabel]="placeholder ? 'always' : 'auto'">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id"
>{{field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span></mat-label
>
<span matTextPrefix class="adf-amount-widget__prefix-spacing">{{ currency }}&nbsp;</span>
<input
@@ -24,6 +24,10 @@
align-self: flex-end;
}
.adf-input {
margin-top: 3px;
}
&:not(#{ms.$mat-focused}):not(#{ms.$mat-form-field-invalid}) {
.adf-amount-widget__prefix-spacing {
color: var(--adf-theme-foreground-secondary-text-color);
@@ -192,7 +192,7 @@ describe('AmountWidgetComponent - rendering', () => {
expect(inputField).toBeTruthy();
expect(await field.getPrefixText()).toBe('$');
const widgetLabel = testingUtils.getByCSS('label.adf-label').nativeElement;
const widgetLabel = testingUtils.getByCSS('.adf-label').nativeElement;
expect(widgetLabel.textContent.trim()).toBe('Test Amount*');
expect(widget.field.isValid).toBe(false);
@@ -228,7 +228,7 @@ describe('AmountWidgetComponent - rendering', () => {
fixture.detectChanges();
await fixture.whenStable();
const widgetLabel = testingUtils.getByCSS('label.adf-label').nativeElement;
const widgetLabel = testingUtils.getByCSS('.adf-label').nativeElement;
expect(widgetLabel.textContent.trim()).toBe('Test Amount*');
const field = await testingUtils.getMatFormField();
@@ -11,9 +11,9 @@
<mat-form-field class="adf-date-time-widget"
[class.adf-left-label-input-datepicker]="field.leftLabels"
[hideRequiredMarker]="true">
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }} ({{ field.dateDisplayFormat }})<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</label>
</mat-label>
<input matInput
[matDatetimepicker]="datetimePicker"
[id]="field.id"
@@ -10,9 +10,9 @@
<div>
<mat-form-field [hideRequiredMarker]="true">
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</label>
</mat-label>
<input matInput
class="adf-input"
@@ -2,9 +2,9 @@
[class.adf-invalid]="!field.isValid && isTouched()"
[class.adf-readonly]="field.readOnly">
<mat-form-field floatPlaceholder="never" [hideRequiredMarker]="true">
<label class="adf-label" [attr.for]="field.id">
<mat-label class="adf-label" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</label>
</mat-label>
<textarea matInput
class="adf-input"
[cdkTextareaAutosize]="true"
@@ -10,9 +10,9 @@
</div>
<div>
<mat-form-field [hideRequiredMarker]="true">
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
<mat-label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</label>
</mat-label>
<input matInput
class="adf-input"
type="text"
@@ -21,7 +21,7 @@
[required]="isRequired()"
[value]="displayValue"
[(ngModel)]="field.value"
(ngModelChange)="onFieldChanged(field)"
(ngModelChange)="onNumberChange($event)"
[disabled]="field.readOnly"
[placeholder]="field.placeholder"
[title]="field.tooltip"
@@ -23,23 +23,100 @@ import { MatInputModule } from '@angular/material/input';
import { CoreTestingModule, UnitTestingUtils } from '../../../../testing';
import { FormFieldModel, FormFieldTypes, FormModel } from '../core';
import { NumberWidgetComponent } from './number.widget';
import { DecimalNumberPipe } from '../../../../pipes';
describe('NumberWidgetComponent', () => {
let loader: HarnessLoader;
let widget: NumberWidgetComponent;
let fixture: ComponentFixture<NumberWidgetComponent>;
let testingUtils: UnitTestingUtils;
let mockDecimalNumberPipe: jasmine.SpyObj<DecimalNumberPipe>;
beforeEach(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
mockDecimalNumberPipe = jasmine.createSpyObj('DecimalNumberPipe', ['transform']);
await TestBed.configureTestingModule({
imports: [CoreTestingModule, MatInputModule, MatIconModule]
});
})
.overrideComponent(NumberWidgetComponent, {
set: {
providers: [{ provide: DecimalNumberPipe, useValue: mockDecimalNumberPipe }]
}
})
.compileComponents();
fixture = TestBed.createComponent(NumberWidgetComponent);
widget = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
testingUtils = new UnitTestingUtils(fixture.debugElement, loader);
});
it('should create', () => {
expect(widget).toBeTruthy();
});
describe('with readonly true', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.NUMBER,
value: 123.45,
id: 'number-id',
readOnly: true
});
});
it('should set displayValue using decimalNumberPipe', () => {
const expectedValue = '2000';
mockDecimalNumberPipe.transform.and.returnValue(expectedValue);
fixture.detectChanges();
expect(mockDecimalNumberPipe.transform).toHaveBeenCalled();
expect(widget.displayValue).toBe(expectedValue);
});
});
describe('with default value', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.NUMBER,
value: 123,
id: 'number-id',
readOnly: false
});
fixture.detectChanges();
});
it('should display the value', async () => {
const input = await testingUtils.getMatInput();
expect(widget.displayValue).toBe(123);
expect(await input.getValue()).toBe('123');
expect(widget.field.value).toBe(123);
});
it('should have value null when field is cleared', async () => {
const input = await testingUtils.getMatInput();
await input.setValue('');
expect(widget.field.value).toBeNull();
});
it('should have value null when value is undefined', async () => {
const input = await testingUtils.getMatInput();
await input.setValue(undefined);
expect(widget.field.value).toBeNull();
});
it('should have value null when value is null', async () => {
const input = await testingUtils.getMatInput();
await input.setValue(null);
expect(widget.field.value).toBeNull();
});
});
describe('when tooltip is set', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
@@ -62,4 +62,12 @@ export class NumberWidgetComponent extends WidgetComponent implements OnInit {
this.displayValue = this.field.value;
}
}
protected onNumberChange(value: string) {
if (value === null || value === undefined || value === '') {
this.field.value = null;
}
this.onFieldChanged(this.field);
}
}
@@ -9,9 +9,9 @@
</div>
<div>
<mat-form-field [hideRequiredMarker]="true">
<label *ngIf="!field.leftLabels" class="adf-label" [attr.for]="field.id">
<mat-label *ngIf="!field.leftLabels" class="adf-label" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" [style.visibility]="isRequired() ? 'visible' : 'hidden'">*</span>
</label>
</mat-label>
<input matInput
class="adf-input"
type="text"
+1
View File
@@ -23,6 +23,7 @@ export * from './components/form-renderer.component';
export * from './components/widgets';
export * from './components/middlewares/middleware';
export * from './components/middlewares/decimal-middleware.service';
export * from './components/helpers/buttons-visibility';
export * from './services/form-rendering.service';
export * from './services/form.service';
+3 -2
View File
@@ -18,7 +18,7 @@
"COMPLETE": "Abschließen",
"CANCEL": "Abbrechen",
"CLAIM": "Beanspruchen",
"UNCLAIM": "Freigeben ",
"UNCLAIM": "FREIGEBEN",
"START PROCESS": "Prozess starten",
"DATA_LOADING": "Daten werden geladen",
"CLOSE": "Schließen",
@@ -452,7 +452,8 @@
"CANCEL": "Abbrechen",
"RESET": "Zurücksetzen",
"THUMBNAILS": "Dokumenten-Miniaturansichten",
"THUMBNAILS_PANLEL_CLOSE": "Miniaturansichten-Fenster schließen"
"THUMBNAILS_PANLEL_CLOSE": "Miniaturansichten-Fenster schließen",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Angezeigt",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Cancel",
"RESET": "Reset",
"THUMBNAILS": "Document thumbnails",
"THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel"
"THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Showing",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Cancelar",
"RESET": "Reiniciar",
"THUMBNAILS": "Miniaturas de documento",
"THUMBNAILS_PANLEL_CLOSE": "Cerrar panel de miniaturas"
"THUMBNAILS_PANLEL_CLOSE": "Cerrar panel de miniaturas",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Mostrando",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Annuler",
"RESET": "Réinitialiser",
"THUMBNAILS": "Miniatures des documents",
"THUMBNAILS_PANLEL_CLOSE": "Fermer le panneau des miniatures"
"THUMBNAILS_PANLEL_CLOSE": "Fermer le panneau des miniatures",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Affichage en cours",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Annulla",
"RESET": "Reimposta",
"THUMBNAILS": "Miniature documento",
"THUMBNAILS_PANLEL_CLOSE": "Chiudi pannello miniature"
"THUMBNAILS_PANLEL_CLOSE": "Chiudi pannello miniature",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Visualizzazione di",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Anuluj",
"RESET": "Resetuj",
"THUMBNAILS": "Miniatura dokumentu",
"THUMBNAILS_PANLEL_CLOSE": "Zamknij panel miniatur"
"THUMBNAILS_PANLEL_CLOSE": "Zamknij panel miniatur",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "Wyświetlanie",
+2 -1
View File
@@ -452,7 +452,8 @@
"CANCEL": "Cancelar",
"RESET": "Redefinir",
"THUMBNAILS": "Miniaturas do documento",
"THUMBNAILS_PANLEL_CLOSE": "Fechar painel de miniaturas"
"THUMBNAILS_PANLEL_CLOSE": "Fechar painel de miniaturas",
"LOADING": "Document is loading"
},
"PAGE_LABEL": {
"SHOWING": "A mostrar",
-2
View File
@@ -8,7 +8,6 @@
@import './mat-selectors';
@import '../form/components/widgets/form.theme';
@import '../clipboard/clipboard.theme';
@import './snackbar.theme';
@import './material.theme';
@mixin alfresco-material-theme($theme) {
@@ -115,7 +114,6 @@
}
@include datetimepicker-theme.mat-datetimepicker-theme($theme);
@include adf-snackbar-theme;
@include adf-material-theme;
}
@@ -1,13 +0,0 @@
@mixin adf-snackbar-theme() {
.adf-error-snackbar {
background-color: var(--theme-warn-color);
}
.adf-warning-snackbar {
background-color: var(--theme-accent-color);
}
.adf-info-snackbar {
background-color: var(--theme-primary-color);
}
}
@@ -33,7 +33,8 @@
aria-expanded="true">
<div id="loader-container" class="adf-loader-container">
<div class="adf-loader-item">
<mat-progress-bar class="adf-loader-item-progress-bar" mode="indeterminate" />
<mat-progress-bar [attr.aria-label]="'ADF_VIEWER.ARIA.LOADING' | translate"
class="adf-loader-item-progress-bar" mode="indeterminate" />
</div>
</div>
</div>
@@ -254,7 +254,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
return pdfDocument.getPage(1);
})
.then(() => this.scalePage('init'))
.then(() => {
setTimeout(() => this.scalePage('init'));
})
.catch(() => this.error.emit());
});
}
@@ -618,8 +620,8 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
*
*/
onPagesLoaded() {
this.pagesLoaded.emit();
this.isPanelDisabled = false;
this.pagesLoaded.emit();
}
/**
@@ -42,6 +42,7 @@ describe('Text View component', () => {
describe('View', () => {
it('Should text container be present with urlFile', async () => {
spyOn(component.contentLoaded, 'emit');
fixture.detectChanges();
const urlFile = './fake-test-file.txt';
const change = new SimpleChange(null, urlFile, true);
@@ -52,9 +53,11 @@ describe('Text View component', () => {
await fixture.whenStable();
expect(testingUtils.getByCSS('.adf-txt-viewer-content').nativeElement.textContent).toContain('example');
expect(component.contentLoaded.emit).toHaveBeenCalled();
});
it('Should text container be present with Blob file', async () => {
spyOn(component.contentLoaded, 'emit');
const blobFile = new Blob(['text example'], { type: 'text/txt' });
const change = new SimpleChange(null, blobFile, true);
@@ -65,6 +68,7 @@ describe('Text View component', () => {
await fixture.whenStable();
expect(testingUtils.getByCSS('.adf-txt-viewer-content').nativeElement.textContent).toContain('example');
expect(component.contentLoaded.emit).toHaveBeenCalled();
});
});
});
@@ -16,7 +16,7 @@
*/
import { HttpClient } from '@angular/common/http';
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { AppConfigService } from '../../../app-config';
@Component({
@@ -34,6 +34,9 @@ export class TxtViewerComponent implements OnChanges {
@Input()
blobFile: Blob;
@Output()
contentLoaded = new EventEmitter<void>();
content: string | ArrayBuffer;
constructor(private http: HttpClient, private appConfigService: AppConfigService) {}
@@ -67,6 +70,9 @@ export class TxtViewerComponent implements OnChanges {
},
(event) => {
reject(event);
},
() => {
this.contentLoaded.emit();
}
);
});
@@ -78,13 +84,17 @@ export class TxtViewerComponent implements OnChanges {
reader.onload = () => {
this.content = reader.result;
resolve();
};
reader.onerror = (error: any) => {
reject(error);
};
reader.onloadend = () => {
this.contentLoaded.emit();
resolve();
};
reader.readAsText(blob);
});
}
@@ -1,95 +1,110 @@
<div *ngIf="(viewerType === 'media' || viewerType === 'pdf' || viewerType === 'image') ? isLoading || !isContentReady : isLoading"
class="adf-viewer-render-main-loader">
<div *ngIf="isLoading" class="adf-viewer-render-main-loader">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container">
<div class="adf-viewer-render__loading-screen ">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div class="adf-viewer-render__loading-screen">
<h2 id="loading-spinner-label">{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner class="adf-viewer-render__loading-screen__spinner"/>
<mat-spinner aria-labelledby="loading-spinner-label"
class="adf-viewer-render__loading-screen__spinner" />
</div>
</div>
</div>
</div>
</div>
<div *ngIf="!isLoading"
class="adf-viewer-render-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension *ngIf="!!externalViewer"
[id]="externalViewer.component"
[url]="urlFile"
[extension]="externalViewer.fileExtension"
[nodeId]="nodeId"
[attr.data-automation-id]="externalViewer.component" />
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer [thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="isContentReady = true"
(close)="onClose()"
(error)="onUnsupportedFile()" />
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFile"
[readOnly]="readOnly"
[fileName]="internalFileName"
[allowedEditActions]="allowedEditActions"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
(imageLoaded)="isContentReady = true"
(isSaving)="isSaving.emit($event)"
/>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player id="adf-mdedia-player"
[urlFile]="urlFile"
[tracks]="tracks"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()"
(canPlay)="isContentReady = true"/>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile"
[blobFile]="blobFile" />
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension *ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[url]="urlFile"
[extension]="extension"
[nodeId]="nodeId"
[attr.data-automation-id]="ext.component" />
@if (urlFile || blobFile) {
<div [style.visibility]="isLoading ? 'hidden' : 'visible'" class="adf-viewer-render-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension
*ngIf="!!externalViewer"
[id]="externalViewer.component"
[url]="urlFile"
[extension]="externalViewer.fileExtension"
[nodeId]="nodeId"
[attr.data-automation-id]="externalViewer.component"
(contentLoaded)="markAsLoaded()"
/>
</ng-container>
<ng-container *ngFor="let extensionTemplate of extensionTemplates">
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template [ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }" />
</span>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer
[thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="markAsLoaded()"
(close)="onClose()"
(error)="onUnsupportedFile()"
/>
</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format [customError]="customError"/>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer
[urlFile]="urlFile"
[readOnly]="readOnly"
[fileName]="internalFileName"
[allowedEditActions]="allowedEditActions"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
(imageLoaded)="markAsLoaded()"
(isSaving)="isSaving.emit($event)"
/>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player
id="adf-mdedia-player"
[urlFile]="urlFile"
[tracks]="tracks"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()"
(canPlay)="markAsLoaded()"
/>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile" [blobFile]="blobFile" (contentLoaded)="markAsLoaded()" />
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension
*ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[url]="urlFile"
[extension]="extension"
[nodeId]="nodeId"
[attr.data-automation-id]="ext.component"
(contentLoaded)="markAsLoaded()"
/>
</ng-container>
<ng-container *ngFor="let extensionTemplate of extensionTemplates">
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension, markAsLoaded: markAsLoaded.bind(this) }"
/>
</span>
</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format [customError]="customError" />
</ng-container>
</div>
</div>
</div>
</div>
}
<ng-container *ngIf="viewerTemplateExtensions">
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector" />
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension, markAsLoaded: markAsLoaded.bind(this) }"
[ngTemplateOutletInjector]="injector" />
</ng-container>
@@ -24,7 +24,7 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NoopTranslateModule, UnitTestingUtils } from '../../../testing';
import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component';
import { ImgViewerComponent, MediaPlayerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
import { ImgViewerComponent, MediaPlayerComponent, PdfViewerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
@@ -278,6 +278,9 @@ describe('ViewerComponent', () => {
fixtureCustom.detectChanges();
await fixtureCustom.whenStable();
fixtureCustom.componentInstance.viewer1.markAsLoaded();
fixtureCustom.detectChanges();
let customContent = getCustomViewerContent(fixtureCustom);
expect(customComponent.extensionsSupportedByTemplates).toEqual(['json', 'test']);
expect(customComponent.extensionTemplates.length).toBe(2);
@@ -291,6 +294,9 @@ describe('ViewerComponent', () => {
fixtureCustom.detectChanges();
await fixtureCustom.whenStable();
fixtureCustom.componentInstance.viewer1.markAsLoaded();
fixtureCustom.detectChanges();
customContent = getCustomViewerContent(fixtureCustom);
expect(customComponent.extensionTemplates[0].isVisible).toBeFalse();
expect(customComponent.extensionTemplates[1].isVisible).toBeTrue();
@@ -410,6 +416,21 @@ describe('ViewerComponent', () => {
expect(component.isSaving.emit).toHaveBeenCalledWith(true);
});
it('should render invisible container during loading', async () => {
component.urlFile = 'fake-url-file.png';
component.viewerType = 'image';
fixture.detectChanges();
component.ngOnChanges();
component.isLoading = true;
await fixture.whenStable();
const container: HTMLDivElement = testingUtils.getByCSS('.adf-viewer-render-main')?.nativeElement;
expect(container).toBeDefined();
expect(container.style.display).not.toBe('none');
expect(container.clientWidth).toBe(0);
expect(container.clientHeight).toBeGreaterThan(0);
});
describe('Attribute', () => {
it('should urlFile present not thrown any error ', () => {
expect(() => {
@@ -506,23 +527,22 @@ describe('ViewerComponent', () => {
expect(component.viewerType).toBe('media');
});
// eslint-disable-next-line ban/ban
xit('should show spinner until content is ready when viewerType is pdf', () => {
component.isLoading = false;
it('should display spinner when viewerType is pdf', () => {
component.urlFile = 'some-url.pdf';
expect(getMainLoader()).toBeNull();
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
const imgViewer = testingUtils.getByDirective(PdfViewerComponent);
imgViewer.triggerEventHandler('pagesLoaded', null);
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('pdf');
});
it('should show spinner until content is ready when viewerType is image', () => {
it('should show spinner until renderer calls markAsLoaded', () => {
spyOn(component, 'markAsLoaded').and.callThrough();
component.isLoading = false;
component.urlFile = 'some-url.png';
@@ -536,17 +556,18 @@ describe('ViewerComponent', () => {
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('image');
expect(component.markAsLoaded).toHaveBeenCalled();
});
it('should not show spinner when isLoading = false and isContentReady = false for other viewer types', () => {
component.isLoading = false;
component.urlFile = 'some-url.txt';
component.ngOnChanges();
it('should not show spinner and set viewerType to unknown if subsequent renderer throws an error', () => {
component.urlFile = 'some-url.png';
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
component.onUnsupportedFile();
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.isContentReady).toBeFalse();
expect(component.viewerType).toBe('unknown');
});
});
});
@@ -86,10 +86,6 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
@Input()
fileName: string;
/** Override loading status */
@Input()
isLoading = false;
/** Enable when where is possible the editing functionalities */
@Input()
readOnly = true;
@@ -142,7 +138,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
extension: string;
internalFileName: string;
viewerType: string = 'unknown';
isContentReady = false;
isLoading = false;
/**
* Returns a list of the active Viewer content extensions.
@@ -182,12 +178,11 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
ngOnInit() {
this.cacheTypeForContent = 'no-cache';
this.isLoading = true;
}
ngOnChanges() {
this.isContentReady = false;
this.isLoading = !this.blobFile && !this.urlFile;
this.isLoading = true;
if (this.blobFile) {
this.setUpBlobData();
} else if (this.urlFile) {
@@ -195,9 +190,16 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
}
}
markAsLoaded() {
this.isLoading = false;
}
private setUpBlobData() {
this.internalFileName = this.fileName;
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type);
if (this.viewerType === 'unknown') {
this.isLoading = false;
}
this.extensionChange.emit(this.blobFile.type);
this.scrollTop();
@@ -207,6 +209,9 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
this.viewerType = this.viewUtilService.getViewerType(this.extension, this.mimeType, this.extensionsSupportedByTemplates);
if (this.viewerType === 'unknown') {
this.isLoading = false;
}
this.extensionChange.emit(this.extension);
this.scrollTop();
@@ -230,6 +235,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
onUnsupportedFile() {
this.viewerType = 'unknown';
this.isLoading = false;
}
onClose() {
@@ -10,6 +10,8 @@
<ng-container *ngIf="showToolbar && !toolbar">
<adf-toolbar id="adf-viewer-toolbar" class="adf-viewer-toolbar">
<adf-toolbar-title>
<adf-toolbar-divider />
<ng-container *ngIf="allowLeftSidebar">
<button mat-icon-button
[attr.aria-expanded]="showLeftSidebar"
@@ -21,7 +23,6 @@
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<button *ngIf="allowGoBack && closeButtonPosition === CloseButtonPosition.Left"
class="adf-viewer-close-button"
data-automation-id="adf-toolbar-left-back"
@@ -31,9 +32,6 @@
(click)="onClose()">
<mat-icon>close</mat-icon>
</button>
</adf-toolbar-title>
<div class="adf-viewer__file-title">
<button *ngIf="allowNavigate && canNavigateBefore"
data-automation-id="adf-toolbar-pref-file"
mat-icon-button
@@ -42,16 +40,6 @@
(click)="onNavigateBeforeClick($event)">
<mat-icon>navigate_before</mat-icon>
</button>
<img class="adf-viewer__mimeicon"
[alt]="'ADF_VIEWER.ARIA.MIME_TYPE_ICON' | translate"
[src]="mimeTypeIconUrl"
data-automation-id="adf-file-thumbnail">
<div class="adf-viewer__display-name"
id="adf-viewer-display-name"
[title]="fileName">
<span class="adf-viewer__display-name-without-extension">{{ fileNameWithoutExtension }}</span>
<span class="adf-viewer__display-name-extension">{{ fileExtension }}</span>
</div>
<button *ngIf="allowNavigate && canNavigateNext"
data-automation-id="adf-toolbar-next-file"
mat-icon-button
@@ -60,7 +48,7 @@
(click)="onNavigateNextClick($event)">
<mat-icon>navigate_next</mat-icon>
</button>
</div>
</adf-toolbar-title>
<ng-content select="adf-viewer-toolbar-actions" />
@@ -79,8 +67,6 @@
</mat-menu>
</ng-container>
<adf-toolbar-divider />
<ng-content select="adf-viewer-toolbar-custom-actions" />
<button id="adf-viewer-fullscreen"
@@ -95,7 +81,6 @@
<ng-container *ngIf="allowRightSidebar && !hideInfoButton">
<adf-toolbar-divider />
<button mat-icon-button
[attr.aria-expanded]="showRightSidebar"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.INFO' | translate"
@@ -105,7 +90,6 @@
(click)="toggleRightSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<ng-container *ngIf="mnuMoreActions">
@@ -125,7 +109,6 @@
<ng-container *ngIf="allowGoBack && closeButtonPosition === CloseButtonPosition.Right">
<adf-toolbar-divider />
<button class="adf-viewer-close-button"
data-automation-id="adf-toolbar-right-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
@@ -136,11 +119,21 @@
</button>
</ng-container>
<div class="adf-viewer__file-title">
<img class="adf-viewer__mimeicon"
[alt]="'ADF_VIEWER.ARIA.MIME_TYPE_ICON' | translate"
[src]="mimeTypeIconUrl"
data-automation-id="adf-file-thumbnail">
<div class="adf-viewer__display-name"
id="adf-viewer-display-name"
[title]="fileName">
<span>{{ displayName }}</span>
</div>
</div>
</adf-toolbar>
</ng-container>
<div class="adf-viewer-sidebars">
<ng-container *ngIf="allowRightSidebar && showRightSidebar">
<div class="adf-viewer__sidebar adf-viewer__sidebar__right"
id="adf-right-sidebar">
@@ -176,7 +169,6 @@
[viewerTemplateExtensions]="viewerExtensions ?? viewerTemplateExtensions"
[nodeId]="nodeId"
[customError]="customError" />
</div>
</div>
</div>
@@ -35,6 +35,10 @@
&-toolbar {
#{ms.$mat-toolbar} {
background-color: var(--adf-theme-background-card-color-087);
adf-toolbar-divider:first-child {
display: none;
}
}
}
@@ -55,23 +59,10 @@
&__display-name {
font-size: var(--theme-subheading-2-font-size);
opacity: 0.87;
line-height: 1.5;
letter-spacing: -0.4px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
vertical-align: middle;
color: var(--adf-theme-foreground-text-color);
&-without-extension {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
max-width: 400px;
white-space: nowrap;
vertical-align: bottom;
}
white-space: nowrap;
}
&-container {
@@ -15,11 +15,12 @@
* limitations under the License.
*/
import { Component } from '@angular/core';
import { Component, SimpleChanges } from '@angular/core';
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { AppConfigService } from '../../app-config';
import { EventMock } from '../../mock';
@@ -143,36 +144,61 @@ describe('ViewerComponent', () => {
});
});
describe('File Name Test', () => {
const getFileNameWithoutExtension = (): string =>
testingUtils.getByCSS('.adf-viewer__display-name-without-extension').nativeElement.textContent;
const getExtension = (): string => testingUtils.getByCSS('.adf-viewer__display-name-extension').nativeElement.textContent;
describe('File Name Display Tests', () => {
describe('displayFileName method', () => {
it('should return full filename when total length is 80 characters or less', () => {
const fileShortName = 'shortname.txt';
component.fileName = fileShortName;
fixture.detectChanges();
it('should fileName be set by urlFile input if the fileName is not provided as Input', () => {
component.fileName = '';
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges: any = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } };
expect(component.getDisplayFileName()).toBe(fileShortName);
expect(getFileName()).toBe(fileShortName);
});
component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges();
it('should truncate filename when total length exceeds 80 characters', () => {
const longName =
'verylongfilenamethatexceedsmaximumlengthallowedverylongfilenamethatexceedsmaximumlengthallowed.verylongextensionnamethatistoolongverylongextensionnamethatistoolong';
expect(getFileName()).toEqual('fakeFileName.jpeg');
expect(getFileNameWithoutExtension()).toBe('fakeFileName.');
expect(getExtension()).toBe('jpeg');
component.fileName = longName;
fixture.detectChanges();
const result = component.getDisplayFileName();
expect(result).toContain('.....');
expect(result.length).toBe(50);
});
it('should handle empty filename', () => {
component.fileName = '';
fixture.detectChanges();
expect(component.getDisplayFileName()).toBe('');
expect(getFileName()).toBe('');
});
});
it('should set fileName providing fileName input', () => {
component.fileName = 'testFileName.jpg';
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges: any = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } };
describe('fileName setter integration', () => {
it('should fileName be set by urlFile input if the fileName is not provided as Input', () => {
component.fileName = '';
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } } as unknown as SimpleChanges;
component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges();
fixture.detectChanges();
component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges();
expect(getFileName()).toEqual('testFileName.jpg');
expect(getFileNameWithoutExtension()).toBe('testFileName.');
expect(getExtension()).toBe('jpg');
expect(getFileName()).toEqual('fakeFileName.jpeg');
});
it('should set fileName providing fileName input', () => {
component.fileName = 'testFileName.jpg';
spyOn(viewUtilService, 'getFilenameFromUrl').and.returnValue('fakeFileName.jpeg');
const mockSimpleChanges = { urlFile: { currentValue: 'https://fakefile.url/fakeFileName.jpeg' } } as unknown as SimpleChanges;
component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges();
expect(getFileName()).toEqual('testFileName.jpg');
});
});
});
@@ -670,4 +696,67 @@ describe('ViewerComponent', () => {
expect(component.downloadFile.emit).toHaveBeenCalled();
}));
});
describe('ViewerComponent toolbar separators', () => {
let component: ViewerComponent<any>;
let fixture: ComponentFixture<ViewerComponent<any>>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MatButtonModule, MatIconModule, MatDialogModule],
declarations: [ViewerComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ViewerComponent);
component = fixture.componentInstance;
component.showViewer = true;
fixture.detectChanges();
});
const createToolbarTest = (
allowRightSidebar: boolean,
hideInfoButton: boolean,
allowFullScreen: boolean,
allowGoBack: boolean,
expectedSeparatorCount: number
) => {
it(`should have correct UI state when [R:${allowRightSidebar}, I:${hideInfoButton}, F:${allowFullScreen}, B:${allowGoBack}]`, () => {
component.allowRightSidebar = allowRightSidebar;
component.hideInfoButton = hideInfoButton;
component.allowFullScreen = allowFullScreen;
component.allowGoBack = allowGoBack;
fixture.detectChanges();
const separators = fixture.debugElement.queryAll(
// .adf-toolbar-divider is the class used for separators
// If your divider uses a different selector, update here
By.css('adf-toolbar-divider')
);
expect(separators.length).toBe(expectedSeparatorCount);
});
};
describe('Toolbar state combinations', () => {
createToolbarTest(true, false, true, true, 2);
createToolbarTest(false, false, true, true, 1);
createToolbarTest(true, true, true, true, 1);
createToolbarTest(true, false, false, true, 1);
createToolbarTest(true, false, true, false, 2);
createToolbarTest(false, true, false, true, 0);
createToolbarTest(false, true, false, false, 0);
});
it('should not show unnecessary separator when close button is disabled but other controls exist', () => {
component.allowGoBack = false;
component.allowFullScreen = true;
component.allowRightSidebar = true;
component.hideInfoButton = false;
fixture.detectChanges();
const separators = fixture.debugElement.queryAll(By.css('adf-toolbar-divider'));
expect(separators.length).toBe(2);
});
});
});
@@ -297,6 +297,7 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
private _fileNameWithoutExtension: string;
private _fileExtension: string;
public displayName: string;
public downloadPromptTimer: number;
public downloadPromptReminderTimer: number;
public mimeTypeIconUrl: string;
@@ -309,6 +310,7 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
this._fileName = fileName;
this._fileExtension = this.viewUtilsService.getFileExtension(this.fileName);
this._fileNameWithoutExtension = this.fileName?.replace(new RegExp(`${this.fileExtension}$`), '') || '';
this.displayName = this.getDisplayFileName();
}
get fileName(): string {
@@ -462,6 +464,24 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
this.clearDownloadPromptTimeouts();
}
getDisplayFileName(): string {
const fullName = (this.fileNameWithoutExtension || '') + (this.fileExtension || '');
const maxLength = 50;
if (fullName.length <= maxLength) {
return fullName;
}
const amountOfTruncateDots = 5;
const availableSpace = maxLength - amountOfTruncateDots;
const endLength = 8;
const startLength = availableSpace - endLength;
const start = fullName.substring(0, startLength);
const end = fullName.substring(fullName.length - endLength);
return start + '.....' + end;
}
private configureAndInitDownloadPrompt() {
this.configureDownloadPromptProperties();
if (this.enableDownloadPrompt) {
@@ -15,8 +15,22 @@
* limitations under the License.
*/
import { Component, Input, ComponentRef, OnInit, ViewChild, ViewContainerRef, OnDestroy, OnChanges } from '@angular/core';
import {
Component,
Input,
ComponentRef,
OnInit,
ViewChild,
ViewContainerRef,
OnDestroy,
OnChanges,
EventEmitter,
Output,
DestroyRef,
inject
} from '@angular/core';
import { ExtensionService } from '../../services/extension.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'adf-preview-extension',
@@ -43,6 +57,11 @@ export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
@Input()
extension: string;
@Output()
contentLoaded = new EventEmitter<void>();
private readonly destroyRef = inject(DestroyRef);
private componentRef: ComponentRef<any>;
constructor(private extensionService: ExtensionService) {}
@@ -73,11 +92,15 @@ export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
private updateInstance() {
if (this.componentRef?.instance) {
const instance = this.componentRef.instance;
this.componentRef.setInput('url', this.url);
this.componentRef.setInput('extension', this.extension);
this.componentRef.setInput('nodeId', this.nodeId);
instance.url = this.url;
instance.extension = this.extension;
instance.nodeId = this.nodeId;
if (this.componentRef.instance?.contentLoaded) {
this.componentRef.instance.contentLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.contentLoaded.emit();
});
}
}
}
}
@@ -24,6 +24,7 @@ import { BaseApi } from './base.api';
import { buildCollectionParam } from '../../../alfrescoApiClient';
import { throwIfNotDefined } from '../../../assert';
import { RecordsIncludeQuery, RecordsPagingQuery, RecordsSourceQuery } from './types';
import { FilePlanRolePaging, FilePlanRoleParameters } from '../model';
/**
* FilePlansApi service.
@@ -158,4 +159,32 @@ export class FilePlansApi extends BaseApi {
returnType: FilePlanEntry
});
}
/**
* Gets a list of roles for the specified file plan.
* @param filePlanId The identifier of a file plan. You can also use the -filePlan- alias.
* @param parameters Optional parameters
* @returns Promise<FilePlanEntry>
*/
getFilePlanRoles(filePlanId: string, parameters?: FilePlanRoleParameters): Promise<FilePlanRolePaging> {
throwIfNotDefined(filePlanId, 'filePlanId');
const whereConditions: string[] = [];
if (parameters?.where) {
if (parameters.where.personId) {
whereConditions.push(`personId='${parameters.where.personId}'`);
}
if (parameters.where.capabilityNames) {
whereConditions.push(`capabilityName in (${parameters.where.capabilityNames.map((value) => "'" + value + "'").join(', ')})`);
}
}
return this.get({
path: '/file-plans/{filePlanId}/roles',
pathParams: {
filePlanId
},
queryParams: {
where: whereConditions.length ? `(${whereConditions.join(' and ')})` : undefined
}
});
}
}
@@ -0,0 +1,12 @@
# FilePlanRole
## Properties
| Name | Type |
|--------------------|-----------------------------------------------------------|
| **displayLabel** | string |
| **groupShortName** | string |
| **name** | string |
| **roleGroupName** | string |
| **capabilities** | [**FilePlanRoleCapability[]**](FilePlanRoleCapability.md) |
@@ -0,0 +1,11 @@
# FilePLanRoleCapability
## Properties
| Name | Type |
|-----------|-------------------------------------------------------------------|
| **group** | [**FilePlanRoleCapabilityGroup**](FilePlanRoleCapabilityGroup.md) |
| **index** | number |
| **name** | string |
| **title** | string |
@@ -0,0 +1,9 @@
# FilePlanRoleCapabilityGroup
## Properties
| Name | Type |
|-----------|--------|
| **id** | string |
| **title** | string |
@@ -0,0 +1,8 @@
# FilePlanRoleEntry
## Properties
| Name | Type |
|-----------|-------------------------------------|
| **entry** | [**FilePlanRole**](FilePlanRole.md) |
@@ -0,0 +1,8 @@
# FilePLanRolePaging
## Properties
| Name | Type |
|----------|---------------------------------------------------------|
| **list** | [**FilePlanRolePagingList**](FilePlanRolePagingList.md) |
@@ -0,0 +1,9 @@
# FilePLanRolePagingList
## Properties
| Name | Type |
|----------------|-------------------------------------------------------------|
| **entries** | [**FilePlanRoleEntry[]**](FilePlanRoleEntry.md) |
| **pagination** | [**Pagination**](../../content-rest-api/docs/Pagination.md) |
@@ -0,0 +1,8 @@
# FilePlanRoleParameters
## Properties
| Name | Type |
|-----------|-------------------------------------------------------------------|
| **where** | [**FilePlanRoleParametersWhere**](FilePlanRoleParametersWhere.md) |
@@ -0,0 +1,9 @@
# FilePlanRoleParametersWhere
## Properties
| Name | Type |
|---------------------|----------|
| **personId** | string |
| **capabilityNames** | string[] |
@@ -2,13 +2,13 @@
All URIs are relative to *https://localhost/alfresco/api/-default-/public/gs/versions/1*
Method | HTTP request | Description
------------- | ------------- | -------------
[**createFilePlanCategories**](FilePlansApi.md#createFilePlanCategories) | **POST** /file-plans/{filePlanId}/categories | Create record categories for a file plan
[**getFilePlan**](FilePlansApi.md#getFilePlan) | **GET** /file-plans/{filePlanId} | Get a file plan
[**getFilePlanCategories**](FilePlansApi.md#getFilePlanCategories) | **GET** /file-plans/{filePlanId}/categories | List file plans's children
[**updateFilePlan**](FilePlansApi.md#updateFilePlan) | **PUT** /file-plans/{filePlanId} | Update a file plan
| Method | HTTP request | Description |
|--------------------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|
| [**createFilePlanCategories**](FilePlansApi.md#createFilePlanCategories) | **POST** /file-plans/{filePlanId}/categories | Create record categories for a file plan |
| [**getFilePlan**](FilePlansApi.md#getFilePlan) | **GET** /file-plans/{filePlanId} | Get a file plan |
| [**getFilePlanCategories**](FilePlansApi.md#getFilePlanCategories) | **GET** /file-plans/{filePlanId}/categories | List file plans's children |
| [**updateFilePlan**](FilePlansApi.md#updateFilePlan) | **PUT** /file-plans/{filePlanId} | Update a file plan |
| [**getFilePlanRoles**](FilePlansApi.md#getFilePlanRoles) | **GET** /file-plans/{filePlanId}/roles | Gets a list of roles for the specified file plan. |
<a name="createFilePlanCategories"></a>
# **createFilePlanCategories**
@@ -402,3 +402,39 @@ parameter are returned in addition to those specified in the **fields** paramete
[**FilePlanEntry**](FilePlanEntry.md)
<a name="getFilePlanRoles"></a>
# **getFilePlanRoles**
> FilePlanEntry getFilePlanRoles(filePlanId, parameters)
Gets a list of roles for the specified file plan.
### Example
```javascript
import FilePlansApi from 'FilePlansApi';
import { AlfrescoApi } from '@alfresco/js-api';
this.alfrescoApi = new AlfrescoApi();
this.alfrescoApi.setConfig({
hostEcm: 'http://127.0.0.1:8080'
});
let fileplansApi = new FilePlansApi(this.alfrescoApi);
const filePlanId = 'some id';
fileplansApi.updateFilePlan(filePlanId, {
where: {
capabilityNames: ['ViewRecords']
}
}).then((data) => console.log('API called successfully. Returned data: ' + data))
.catch((error) => console.log(error));
```
### Parameters
| Name | Type | Description |
|----------------|---------------------------------------------------------|-----------------------------------------------------------------------|
| **filePlanId** | **string** | The identifier of a file plan. You can also use the -filePlan- alias. |
| **parameters** | [**FilePlanRoleParameters**](FilePlanRoleParameters.md) | Optional parameters. |
### Return type
[**FilePlanRolePaging**](FilePlanRolePaging.md)
@@ -0,0 +1,33 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRoleCapability } from './filePlanRoleCapability';
export class FilePlanRole {
displayLabel: string;
groupShortName: string;
name: string;
roleGroupName: string;
capabilities: FilePlanRoleCapability[];
constructor(input?: Partial<FilePlanRole>) {
if (input) {
Object.assign(this, input);
this.capabilities = input.capabilities?.map((capability) => new FilePlanRoleCapability(capability));
}
}
}
@@ -0,0 +1,32 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRoleCapabilityGroup } from './filePlanRoleCapabilityGroup';
export class FilePlanRoleCapability {
group: FilePlanRoleCapabilityGroup;
index: number;
name: string;
title: string;
constructor(input?: Partial<FilePlanRoleCapability>) {
if (input) {
Object.assign(this, input);
this.group = input.group ? new FilePlanRoleCapabilityGroup(input.group) : undefined;
}
}
}
@@ -0,0 +1,27 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class FilePlanRoleCapabilityGroup {
id: string;
title: string;
constructor(input?: Partial<FilePlanRoleCapabilityGroup>) {
if (input) {
Object.assign(this, input);
}
}
}
@@ -0,0 +1,29 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRole } from './filePlanRole';
export class FilePlanRoleEntry {
entry: FilePlanRole;
constructor(input?: Partial<FilePlanRoleEntry>) {
if (input) {
Object.assign(this, input);
this.entry = input.entry ? new FilePlanRole(input.entry) : undefined;
}
}
}
@@ -0,0 +1,29 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRolePagingList } from './filePlanRolePagingList';
export class FilePlanRolePaging {
list: FilePlanRolePagingList;
constructor(input: Partial<FilePlanRolePaging>) {
if (input) {
Object.assign(this, input);
this.list = input.list ? new FilePlanRolePagingList(input.list) : undefined;
}
}
}
@@ -0,0 +1,32 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Pagination } from '../../content-rest-api';
import { FilePlanRoleEntry } from './filePlanRoleEntry';
export class FilePlanRolePagingList {
entries: FilePlanRoleEntry[];
pagination: Pagination;
constructor(input: Partial<FilePlanRolePagingList>) {
if (input) {
Object.assign(this, input);
this.pagination = input.pagination ? new Pagination(input.pagination) : undefined;
this.entries = input?.entries.map((entry) => new FilePlanRoleEntry(entry));
}
}
}
@@ -0,0 +1,22 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilePlanRoleParametersWhere } from './filePlanRoleParametersWhere';
export interface FilePlanRoleParameters {
where?: FilePlanRoleParametersWhere;
}
@@ -0,0 +1,21 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface FilePlanRoleParametersWhere {
personId?: string;
capabilityNames?: string[];
}
@@ -19,6 +19,14 @@ export * from './filePlan';
export * from './filePlanBodyUpdate';
export * from './filePlanComponentBodyUpdate';
export * from './filePlanEntry';
export * from './filePlanRole';
export * from './filePlanRoleCapability';
export * from './filePlanRoleCapabilityGroup';
export * from './filePlanRoleEntry';
export * from './filePlanRolePaging';
export * from './filePlanRolePagingList';
export * from './filePlanRoleParameters';
export * from './filePlanRoleParametersWhere';
export * from './rMNodeBodyCreate';
export * from './rMNodeBodyCreateWithRelativePath';
export * from './rMSite';
@@ -0,0 +1,160 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EcmAuthMock, FilePlansMock } from '../mockObjects';
import { AlfrescoApi, FilePlanRolePaging, FilePlansApi } from '../../src';
describe('FilePlansApi', () => {
let filePlansApiMock: FilePlansMock;
let filePlansApi: FilePlansApi;
beforeEach(async () => {
const hostEcm = 'https://127.0.0.1:8080';
const authResponseMock = new EcmAuthMock(hostEcm);
authResponseMock.get201Response();
filePlansApiMock = new FilePlansMock(hostEcm);
const alfrescoApi = new AlfrescoApi({
hostEcm
});
filePlansApi = new FilePlansApi(alfrescoApi);
await alfrescoApi.login('admin', 'admin');
});
describe('getFilePlanRoles', () => {
const filePlanId = 'filePlanId123';
let expectedRolePaging: FilePlanRolePaging;
beforeEach(() => {
expectedRolePaging = {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
displayLabel: 'Role One',
groupShortName: 'group short name 1',
name: 'role1',
roleGroupName: 'role group name 1',
capabilities: [
{
index: 0,
name: 'capability1',
title: 'Capability One',
group: {
id: 'group1',
title: 'Group One'
}
},
{
index: 1,
name: 'capability2',
title: 'Capability Two',
group: {
id: 'group2',
title: 'Group Two'
}
}
]
}
},
{
entry: {
displayLabel: 'Role Two',
groupShortName: 'group short name 2',
name: 'role2',
roleGroupName: 'role group name 2',
capabilities: [
{
index: 0,
name: 'capability3',
title: 'Capability Three',
group: {
id: 'group3',
title: 'Group Three'
}
}
]
}
}
]
}
};
});
it('should get file plan roles', (done) => {
filePlansApiMock.get200FilePlanRoles(filePlanId);
filePlansApi.getFilePlanRoles(filePlanId).then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
it('should get file plan roles with filtering by capability names', (done) => {
filePlansApiMock.get200FilePlanRolesWithFilteringByCapabilityNames(filePlanId);
filePlansApi
.getFilePlanRoles(filePlanId, {
where: {
capabilityNames: ['capability1', 'capability2']
}
})
.then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
it('should get file plan roles with filtering by person id', (done) => {
filePlansApiMock.get200FilePlanRolesWithFilteringByPersonId(filePlanId);
filePlansApi
.getFilePlanRoles(filePlanId, {
where: {
personId: 'someUser'
}
})
.then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
it('should get file plan roles with filtering by capability names', (done) => {
filePlansApiMock.get200FilePlanRolesWithFilteringByPersonIdAndCapabilityNames(filePlanId);
filePlansApi
.getFilePlanRoles(filePlanId, {
where: {
personId: 'someUser',
capabilityNames: ['capability1', 'capability2']
}
})
.then((rolePaging) => {
expect(rolePaging).toEqual(expectedRolePaging);
done();
});
});
});
});
@@ -0,0 +1,117 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseMock } from '../base.mock';
import nock from 'nock';
import { FilePlanRolePaging } from '@alfresco/js-api';
export class FilePlansMock extends BaseMock {
get200FilePlanRoles(filePlanId: string): void {
this.nock200FilePlanRoles(filePlanId).query({}).reply(200, this.mockFilePlanRolePaging());
}
get200FilePlanRolesWithFilteringByCapabilityNames(filePlanId: string): void {
this.nock200FilePlanRoles(filePlanId)
.query({
where: "(capabilityName in ('capability1', 'capability2'))"
})
.reply(200, this.mockFilePlanRolePaging());
}
get200FilePlanRolesWithFilteringByPersonId(filePlanId: string): void {
this.nock200FilePlanRoles(filePlanId)
.query({
where: "(personId='someUser')"
})
.reply(200, this.mockFilePlanRolePaging());
}
get200FilePlanRolesWithFilteringByPersonIdAndCapabilityNames(filePlanId: string): void {
this.nock200FilePlanRoles(filePlanId)
.query({
where: "(personId='someUser' and capabilityName in ('capability1', 'capability2'))"
})
.reply(200, this.mockFilePlanRolePaging());
}
private nock200FilePlanRoles(filePlanId: string): nock.Interceptor {
return nock(this.host, { encodedQueryParams: true }).get(`/alfresco/api/-default-/public/gs/versions/1/file-plans/${filePlanId}/roles`);
}
private mockFilePlanRolePaging(): FilePlanRolePaging {
return {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
displayLabel: 'Role One',
groupShortName: 'group short name 1',
name: 'role1',
roleGroupName: 'role group name 1',
capabilities: [
{
index: 0,
name: 'capability1',
title: 'Capability One',
group: {
id: 'group1',
title: 'Group One'
}
},
{
index: 1,
name: 'capability2',
title: 'Capability Two',
group: {
id: 'group2',
title: 'Group Two'
}
}
]
}
},
{
entry: {
displayLabel: 'Role Two',
groupShortName: 'group short name 2',
name: 'role2',
roleGroupName: 'role group name 2',
capabilities: [
{
index: 0,
name: 'capability3',
title: 'Capability Three',
group: {
id: 'group3',
title: 'Group Three'
}
}
]
}
}
]
}
};
}
}
+1
View File
@@ -34,6 +34,7 @@ export * from './content-services/version.mock';
export * from './content-services/webscript.mock';
export * from './goverance-services/authority-clearance.mock';
export * from './goverance-services/file-plans.mock';
export * from './goverance-services/gs-sites.mock';
export * from './goverance-services/node-security-marks.mock';
export * from './goverance-services/security-groups.mock';
@@ -81,6 +81,7 @@
padding: 0;
height: 100%;
width: 100%;
position: relative;
&-container {
display: flex;
@@ -670,17 +670,41 @@ describe('FormCloudComponent', () => {
done();
});
const formValues: any[] = [];
const formValues: TaskVariableCloud[] = [
{
name: 'var1',
value: 'value1',
id: 'var1',
type: 'string',
hasValue: () => true
}
];
const change = new SimpleChange(null, formValues, false);
formComponent.data = formValues;
formComponent.ngOnChanges({ data: change });
});
it('should not change form if custom form values is empty array', () => {
const formModel = new FormModel({
id: 'id',
taskId: 'task-id',
fields: [{ id: 'field1' }, { id: 'field2' }]
});
formComponent.form = formModel;
const formValues: TaskVariableCloud[] = [];
const change = new SimpleChange(null, formValues, false);
formComponent.ngOnChanges({ data: change });
expect(formComponent.form).toEqual(formModel);
});
it('should save task form and raise corresponding event', () => {
spyOn(formCloudService, 'saveTaskForm').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);
@@ -773,7 +797,7 @@ describe('FormCloudComponent', () => {
spyOn(formCloudService, 'completeTaskForm').and.callFake(
() =>
new Observable((observer) => {
observer.next();
observer.next(undefined);
observer.complete();
})
);

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