From 49314759500286b24dda1b1a31d10cff02942ab6 Mon Sep 17 00:00:00 2001 From: Shivangi Shree Date: Wed, 12 Aug 2026 23:37:31 +0530 Subject: [PATCH 01/31] =?UTF-8?q?[ACS-10175]=20Add=20logic=20to=20send=20n?= =?UTF-8?q?otification=20only=20if=20the=20user=20adds=20labe=E2=80=A6=20(?= =?UTF-8?q?#12141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ACS-10175] Add logic to send notification only if the user adds label after creating pr * [ACS-10175] Update label detection logic * [ACS-10175] check if pr createdAt and label addedAt is different * [ACS-10175] Add logic to send notification on adding A/N BDU label * [ACS-10175] Add issues to permissions * Make time difference 15 seconds * [ACS-10175] Fix version * [ACS-10175] Add condition checking * [ACS-10175] CR fixes * [ACS-10175] Add full sha --- .github/workflows/notify-on-an-bdu-label.yml | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/notify-on-an-bdu-label.yml b/.github/workflows/notify-on-an-bdu-label.yml index 7a1586d94e..2248248c4e 100644 --- a/.github/workflows/notify-on-an-bdu-label.yml +++ b/.github/workflows/notify-on-an-bdu-label.yml @@ -7,6 +7,7 @@ on: permissions: pull-requests: read + issues: read jobs: notify-bdu: @@ -18,7 +19,40 @@ jobs: github.event.label.name == 'A/N BDU' && github.event.pull_request.state == 'open' steps: + - name: Check if label was added after PR creation (with time threshold) + id: check_label_timing + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const prCreatedAt = new Date('${{ github.event.pull_request.created_at }}'); + + const timeline = await github.rest.issues.listEvents({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const labelEvent = timeline.data.reverse().find(event => + event.event === 'labeled' && + event.label.name === 'A/N BDU' + ); + + if (!labelEvent) { + core.setOutput('should_notify', 'false'); + return; + } + + const labelAddedAt = new Date(labelEvent.created_at); + const timeDiffSeconds = (labelAddedAt - prCreatedAt) / 1000; + + if (timeDiffSeconds > 15) { + core.setOutput('should_notify', 'true'); + } else { + core.setOutput('should_notify', 'false'); + } + - name: Send Teams notification + if: steps.check_label_timing.outputs.should_notify == 'true' uses: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification@5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b # v18.21.3 with: webhook-url: ${{ secrets.TEAMS_NOTIFICATION_ADF_BDU_WEBHOOK }} From 3aa17e49fe990edfd794c03a0210feebec94d800 Mon Sep 17 00:00:00 2001 From: Mykyta Maliarchuk <84377976+nikita-web-ua@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:32:32 +0200 Subject: [PATCH 02/31] [ACS-12332] Add checkout and cancel-checkout node support (#12133) * [ACS-12332] Add checkout and cancel-checkout node support * [ACS-12332] cr fix --- docs/core/services/nodes-api.service.md | 17 +++++ .../common/services/nodes-api.service.spec.ts | 26 +++++++- .../lib/common/services/nodes-api.service.ts | 24 +++++++ .../src/api/content-rest-api/api/nodes.api.ts | 59 +++++++++++++++++ .../src/api/content-rest-api/docs/NodesApi.md | 64 ++++++++++++++++++- .../test/content-services/nodeApi.spec.ts | 24 +++++++ .../mockObjects/content-services/node.mock.ts | 17 +++++ 7 files changed, 229 insertions(+), 2 deletions(-) diff --git a/docs/core/services/nodes-api.service.md b/docs/core/services/nodes-api.service.md index bad26f0fbb..3f3a0a2e08 100644 --- a/docs/core/services/nodes-api.service.md +++ b/docs/core/services/nodes-api.service.md @@ -18,6 +18,7 @@ Accesses and manipulates ACS document nodes using their node IDs. - [Getting folder node contents](#getting-folder-node-contents) - [Creating and updating nodes](#creating-and-updating-nodes) - [Deleting and restoring nodes](#deleting-and-restoring-nodes) + - [Checking out nodes](#checking-out-nodes) - [See also](#see-also) ## Class members @@ -103,6 +104,16 @@ Accesses and manipulates ACS document nodes using their node IDs. - _nodeId:_ `string` - ID of the target node - _opts:_ `{ where?: string; includeSource?: boolean;} & NodesIncludeQuery & ContentPagingQuery` - additional options that API can take - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeAssociationPaging`](../../../lib/js-api/src/api/content-rest-api/docs/NodesApi.md#NodeAssociationPaging)`>` - List of node's parents. +- **checkoutNode**(nodeId: `string`, opts?: `NodesIncludeQuery`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>`
+ Checks out a file node for offline editing. Creates a private working copy and locks the original. + - _nodeId:_ `string` - ID of the file node to check out + - _opts:_ `NodesIncludeQuery` - (Optional) Additional query parameters (`include`, `fields`) + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>` - The working copy node +- **cancelCheckoutNode**(nodeId: `string`, opts?: `NodesIncludeQuery`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>`
+ Cancels a checkout. Accepts either the working copy or the original checked-out node. Deletes the working copy and unlocks the original. + - _nodeId:_ `string` - ID of the working copy or original checked-out node + - _opts:_ `NodesIncludeQuery` - (Optional) Additional query parameters (`include`, `fields`) + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>` - The original (unlocked) node ## Details @@ -160,6 +171,12 @@ and pages in the Alfresco JS API for further details and options. Note that you can also use the [Deleted Nodes Api service](deleted-nodes-api.service.md) get a list of all items currently in the trashcan. +### Checking out nodes + +Use `checkoutNode` to lock a file node for offline editing. This creates a private working copy and locks the original node so other users cannot modify it. On success, the Observable emits the working copy `NodeEntry`. + +Use `cancelCheckoutNode` to discard the working copy and unlock the original. You can pass either the working copy node ID or the original node ID. On success, the Observable emits the original (unlocked) `NodeEntry`. + ## See also - [Deleted nodes api service](deleted-nodes-api.service.md) diff --git a/lib/content-services/src/lib/common/services/nodes-api.service.spec.ts b/lib/content-services/src/lib/common/services/nodes-api.service.spec.ts index 3be4e591ed..8da5a9e199 100644 --- a/lib/content-services/src/lib/common/services/nodes-api.service.spec.ts +++ b/lib/content-services/src/lib/common/services/nodes-api.service.spec.ts @@ -18,11 +18,19 @@ import { TestBed } from '@angular/core/testing'; import { RedirectAuthService } from '@alfresco/adf-core'; import { EMPTY, firstValueFrom, of } from 'rxjs'; -import { JobIdBodyEntry, SizeDetails, SizeDetailsEntry } from '@alfresco/js-api'; +import { JobIdBodyEntry, NodeEntry, SizeDetails, SizeDetailsEntry } from '@alfresco/js-api'; import { NodesApiService } from './nodes-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiServiceMock } from '../../mock/alfresco-api.service.mock'; +const fakeNodeEntry = { + entry: { + id: 'fake-node-id', + name: 'fake-file.txt', + nodeType: 'cm:content' + } +} as NodeEntry; + const fakeInitiateFolderSizeResponse: JobIdBodyEntry = { entry: { jobId: 'fake-job-id' @@ -75,4 +83,20 @@ describe('NodesApiService', () => { expect(nodesApiService.nodesApi.listParents).toHaveBeenCalledWith('fake-node-id', { include: ['path'], where: 'isPrimary=true' }); }); + + it('should call nodesApi.checkoutNode with the node ID and optional query params', async () => { + const opts = { include: ['path', 'allowableOperations'], fields: ['id', 'name'] }; + spyOn(nodesApiService.nodesApi, 'checkoutNode').and.returnValue(Promise.resolve(fakeNodeEntry)); + await firstValueFrom(nodesApiService.checkoutNode(fakeNodeEntry.entry.id, opts)); + + expect(nodesApiService.nodesApi.checkoutNode).toHaveBeenCalledWith(fakeNodeEntry.entry.id, opts); + }); + + it('should call nodesApi.cancelCheckoutNode with the node ID and optional query params', async () => { + const opts = { include: ['path'], fields: ['id'] }; + spyOn(nodesApiService.nodesApi, 'cancelCheckoutNode').and.returnValue(Promise.resolve(fakeNodeEntry)); + await firstValueFrom(nodesApiService.cancelCheckoutNode(fakeNodeEntry.entry.id, opts)); + + expect(nodesApiService.nodesApi.cancelCheckoutNode).toHaveBeenCalledWith(fakeNodeEntry.entry.id, opts); + }); }); diff --git a/lib/content-services/src/lib/common/services/nodes-api.service.ts b/lib/content-services/src/lib/common/services/nodes-api.service.ts index 475695f5bd..ba20a90f9f 100644 --- a/lib/content-services/src/lib/common/services/nodes-api.service.ts +++ b/lib/content-services/src/lib/common/services/nodes-api.service.ts @@ -293,6 +293,30 @@ export class NodesApiService { return from(this.nodesApi.listParents(nodeId, opts)); } + /** + * Checks out a file node for offline editing. + * Creates a private working copy and locks the original. + * + * @param nodeId ID of the node to check out + * @param opts Optional query parameters (`include`, `fields`) + * @returns Observable emitting the working copy node + */ + checkoutNode(nodeId: string, opts?: NodesIncludeQuery): Observable { + return from(this.nodesApi.checkoutNode(nodeId, opts)); + } + + /** + * Cancels a checkout. Accepts either the working copy or the original node. + * Deletes the working copy and unlocks the original. + * + * @param nodeId ID of the working copy or the original checked-out node + * @param opts Optional query parameters (`include`, `fields`) + * @returns Observable emitting the original (unlocked) node + */ + cancelCheckoutNode(nodeId: string, opts?: NodesIncludeQuery): Observable { + return from(this.nodesApi.cancelCheckoutNode(nodeId, opts)); + } + private randomNodeName(): string { return `node_${Date.now()}`; } diff --git a/lib/js-api/src/api/content-rest-api/api/nodes.api.ts b/lib/js-api/src/api/content-rest-api/api/nodes.api.ts index e9882cfa21..c9f740c6be 100644 --- a/lib/js-api/src/api/content-rest-api/api/nodes.api.ts +++ b/lib/js-api/src/api/content-rest-api/api/nodes.api.ts @@ -1033,4 +1033,63 @@ export class NodesApi extends BaseApi { returnType: SizeDetailsEntry }); } + + /** + * Checkout a node + * + * Checks out a file node for offline editing. Creates a private working copy and locks the original. + * + * @param nodeId The identifier of a file node to check out. + * @param opts Optional parameters + * @returns Promise - the working copy node + */ + checkoutNode(nodeId: string, opts?: NodesIncludeQuery): Promise { + throwIfNotDefined(nodeId, 'nodeId'); + + const pathParams = { + nodeId + }; + + const queryParams = { + include: buildCollectionParam(opts?.include, 'csv'), + fields: buildCollectionParam(opts?.fields, 'csv') + }; + + return this.post({ + path: '/nodes/{nodeId}/checkout', + pathParams, + queryParams, + returnType: NodeEntry + }); + } + + /** + * Cancel checkout of a node + * + * Cancels a checkout. Accepts either the working copy or the original checked-out node. + * Deletes the working copy, unlocks the original, and returns the original node. + * + * @param nodeId The identifier of the working copy or the original checked-out node. + * @param opts Optional parameters + * @returns Promise - the original (unlocked) node + */ + cancelCheckoutNode(nodeId: string, opts?: NodesIncludeQuery): Promise { + throwIfNotDefined(nodeId, 'nodeId'); + + const pathParams = { + nodeId + }; + + const queryParams = { + include: buildCollectionParam(opts?.include, 'csv'), + fields: buildCollectionParam(opts?.fields, 'csv') + }; + + return this.post({ + path: '/nodes/{nodeId}/cancel-checkout', + pathParams, + queryParams, + returnType: NodeEntry + }); + } } diff --git a/lib/js-api/src/api/content-rest-api/docs/NodesApi.md b/lib/js-api/src/api/content-rest-api/docs/NodesApi.md index a2be171308..11ca9b0111 100644 --- a/lib/js-api/src/api/content-rest-api/docs/NodesApi.md +++ b/lib/js-api/src/api/content-rest-api/docs/NodesApi.md @@ -25,8 +25,10 @@ All URIs are relative to *https://localhost/alfresco/api/-default-/public/alfres | [unlockNode](#unlockNode) | **POST** /nodes/{nodeId}/unlock | Unlock a node | | [updateNode](#updateNode) | **PUT** /nodes/{nodeId} | Update a node | | [updateNodeContent](#updateNodeContent) | **PUT** /nodes/{nodeId}/content | Update node content | -| [initiateFolderSizeCalculation](#initiateFolderSizeCalculation) | **POST** /nodes/{nodeId}/size-details | Initiate a new request to calculate folder size | +| [initiateFolderSizeCalculation](#initiateFolderSizeCalculation) | **POST** /nodes/{nodeId}/size-details | Initiate a new request to calculate folder size | | [getFolderSizeInfo](#getFolderSizeInfo) | **GET** /nodes/{nodeId}/size-details/{jobId} | Gets the details of a folder | +| [cancelCheckoutNode](#cancelCheckoutNode) | **POST** /nodes/{nodeId}/cancel-checkout | Cancel checkout of a node | +| [checkoutNode](#checkoutNode) | **POST** /nodes/{nodeId}/checkout | Checkout a node | ## copyNode @@ -1260,6 +1262,66 @@ nodesApi.getFolderSizeInfo(``, ``).then((data) => { }); ``` +## checkoutNode + +Checkout a node + +Checks out a file node for offline editing. Creates a private working copy and locks the original. + +**Parameters** + +| Name | Type | Description | +|--------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **nodeId** | string | The identifier of a file node to check out. | +| opts.include | string[] | Returns additional information about the node. The following optional fields can be requested: `allowableOperations`, `association`, `isLink`, `isFavorite`, `isLocked`, `path`, `permissions`, `definition` | +| opts.fields | string[] | A list of field names. You can use this parameter to restrict the fields returned within a response if, for example, you want to save on overall bandwidth. The list applies to a returned individual entity or entries within a collection. If the API method also supports the **include** parameter, then the fields specified in the **include** parameter are returned in addition to those specified in the **fields** parameter. | + +**Return type**: [NodeEntry](NodeEntry.md) + +**Example** + +```javascript +import { AlfrescoApi, NodesApi } from '@alfresco/js-api'; + +const alfrescoApi = new AlfrescoApi(/*..*/); +const nodesApi = new NodesApi(alfrescoApi); +const opts = {}; + +nodesApi.checkoutNode(``, opts).then((data) => { + console.log('API called successfully. Returned data: ' + data); +}); +``` + +## cancelCheckoutNode + +Cancel checkout of a node + +Cancels a checkout. Accepts either the working copy or the original checked-out node. Deletes the working copy, unlocks the original, and returns the original node. + +**Parameters** + +| Name | Type | Description | +|--------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **nodeId** | string | The identifier of the working copy or original checked-out node. | +| opts.include | string[] | Returns additional information about the node. The following optional fields can be requested: `allowableOperations`, `association`, `isLink`, `isFavorite`, `isLocked`, `path`, `permissions`, `definition` | +| opts.fields | string[] | A list of field names. You can use this parameter to restrict the fields returned within a response if, for example, you want to save on overall bandwidth. The list applies to a returned individual entity or entries within a collection. If the API method also supports the **include** parameter, then the fields specified in the **include** parameter are returned in addition to those specified in the **fields** parameter. | + +**Return type**: [NodeEntry](NodeEntry.md) + +**Example** + +```javascript +import { AlfrescoApi, NodesApi } from '@alfresco/js-api'; + +const alfrescoApi = new AlfrescoApi(/*..*/); +const nodesApi = new NodesApi(alfrescoApi); +const opts = {}; + +nodesApi.cancelCheckoutNode(``, opts).then((data) => { + console.log('API called successfully. Returned data: ' + data); +}); +``` + # Models ## NodeBodyUpdate diff --git a/lib/js-api/test/content-services/nodeApi.spec.ts b/lib/js-api/test/content-services/nodeApi.spec.ts index 4b5f6acc65..ec9bde13cb 100644 --- a/lib/js-api/test/content-services/nodeApi.spec.ts +++ b/lib/js-api/test/content-services/nodeApi.spec.ts @@ -141,6 +141,30 @@ describe('Node', () => { }); }); + describe('Checkout', () => { + it('should POST to the checkout endpoint', async () => { + nodeMock.post200CheckoutNode('fake-node-id'); + const result = await nodesApi.checkoutNode('fake-node-id'); + assert.ok(result.entry, 'cancelCheckoutNode should return a NodeEntry'); + }); + + it('should throw if nodeId is not defined', () => { + assert.throws(() => nodesApi.checkoutNode(undefined)); + }); + }); + + describe('Cancel Checkout', () => { + it('should POST to the cancel-checkout endpoint', async () => { + nodeMock.post200CancelCheckoutNode('fake-node-id'); + const result = await nodesApi.cancelCheckoutNode('fake-node-id'); + assert.ok(result.entry, 'checkoutNode should return a NodeEntry'); + }); + + it('should throw if nodeId is not defined', () => { + assert.throws(() => nodesApi.cancelCheckoutNode(undefined)); + }); + }); + describe('FolderInformation', () => { it('should return jobId on initiateFolderSizeCalculation API call if everything is ok', async () => { nodeMock.post200ResponseInitiateFolderSizeCalculation(); diff --git a/lib/js-api/test/mockObjects/content-services/node.mock.ts b/lib/js-api/test/mockObjects/content-services/node.mock.ts index d4e87b691a..5fa5151132 100644 --- a/lib/js-api/test/mockObjects/content-services/node.mock.ts +++ b/lib/js-api/test/mockObjects/content-services/node.mock.ts @@ -16,6 +16,7 @@ */ import { BaseMock } from '../base.mock'; +import { NodeEntry } from '../../../src'; export class NodeMock extends BaseMock { get200ResponseChildren(): void { @@ -284,4 +285,20 @@ export class NodeMock extends BaseMock { } }); } + + post200CheckoutNode(nodeId: string): void { + this.mock() + .post(`/alfresco/api/-default-/public/alfresco/versions/1/nodes/${nodeId}/checkout`) + .reply(200, { + entry: { id: 'test-node-id', name: 'Test Node' } + } as NodeEntry); + } + + post200CancelCheckoutNode(nodeId: string): void { + this.mock() + .post(`/alfresco/api/-default-/public/alfresco/versions/1/nodes/${nodeId}/cancel-checkout`) + .reply(200, { + entry: { id: 'test-node-id', name: 'Test Node' } + } as NodeEntry); + } } From d5772606f5343a6236a34051b78810252d2c8682 Mon Sep 17 00:00:00 2001 From: Dominik Iwanek <141320833+dominikiwanekhyland@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:36:12 +0200 Subject: [PATCH 03/31] [PRODSEC-13780] - Fix Angular i18n: Cross-Site Scripting (XSS) via event-handler attributes (ADF/ACA/ADW) (#12167) --- lib/content-services/package.json | 14 +- lib/core/package.json | 10 +- lib/extensions/package.json | 4 +- lib/insights/package.json | 8 +- lib/process-services-cloud/package.json | 14 +- lib/process-services/package.json | 14 +- package.json | 12 +- pnpm-lock.yaml | 182 ++++++++++++------------ 8 files changed, 129 insertions(+), 129 deletions(-) diff --git a/lib/content-services/package.json b/lib/content-services/package.json index abce4fea81..7e17e7c9ea 100644 --- a/lib/content-services/package.json +++ b/lib/content-services/package.json @@ -12,14 +12,14 @@ }, "peerDependencies": { "@angular/cdk": ">=20.2.14", - "@angular/common": ">=20.3.25", - "@angular/compiler": ">=20.3.25", - "@angular/core": ">=20.3.25", - "@angular/forms": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/compiler": ">=20.3.27", + "@angular/core": ">=20.3.27", + "@angular/forms": ">=20.3.27", "@angular/material": ">=20.2.14", - "@angular/platform-browser": ">=20.3.25", - "@angular/platform-browser-dynamic": ">=20.3.25", - "@angular/router": ">=20.3.25", + "@angular/platform-browser": ">=20.3.27", + "@angular/platform-browser-dynamic": ">=20.3.27", + "@angular/router": ">=20.3.27", "@alfresco/js-api": ">=10.0.0", "@ngx-translate/core": ">=17.0.0", "@alfresco/adf-core": ">=9.0.0" diff --git a/lib/core/package.json b/lib/core/package.json index 56e546aa20..0635b6d84c 100644 --- a/lib/core/package.json +++ b/lib/core/package.json @@ -23,13 +23,13 @@ }, "peerDependencies": { "@angular/cdk": ">=20.2.14", - "@angular/common": ">=20.3.25", - "@angular/core": ">=20.3.25", - "@angular/forms": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/core": ">=20.3.27", + "@angular/forms": ">=20.3.27", "@angular/material": ">=20.2.14", "@angular/material-date-fns-adapter": ">=20.2.14", - "@angular/platform-browser": ">=20.3.25", - "@angular/router": ">=20.3.25", + "@angular/platform-browser": ">=20.3.27", + "@angular/router": ">=20.3.27", "@mat-datetimepicker/core": ">=12.0.1", "@ngx-translate/core": ">=17.0.0", "@alfresco/js-api": ">=10.0.0", diff --git a/lib/extensions/package.json b/lib/extensions/package.json index 9f30b6074f..804d7593dc 100644 --- a/lib/extensions/package.json +++ b/lib/extensions/package.json @@ -12,8 +12,8 @@ "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" }, "peerDependencies": { - "@angular/common": ">=20.3.25", - "@angular/core": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/core": ">=20.3.27", "@alfresco/js-api": ">=10.0.0" }, "keywords": [ diff --git a/lib/insights/package.json b/lib/insights/package.json index 6f4f422df3..bf0225cff7 100644 --- a/lib/insights/package.json +++ b/lib/insights/package.json @@ -16,10 +16,10 @@ "raphael": ">=2.3.0" }, "peerDependencies": { - "@angular/common": ">=20.3.25", - "@angular/compiler": ">=20.3.25", - "@angular/core": ">=20.3.25", - "@angular/forms": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/compiler": ">=20.3.27", + "@angular/core": ">=20.3.27", + "@angular/forms": ">=20.3.27", "@angular/material": ">=20.2.14", "@alfresco/adf-core": ">=9.0.0", "@alfresco/adf-content-services": ">=9.0.0", diff --git a/lib/process-services-cloud/package.json b/lib/process-services-cloud/package.json index 86f933119c..24193bc443 100644 --- a/lib/process-services-cloud/package.json +++ b/lib/process-services-cloud/package.json @@ -12,14 +12,14 @@ }, "peerDependencies": { "@angular/cdk": ">=20.2.14", - "@angular/common": ">=20.3.25", - "@angular/compiler": ">=20.3.25", - "@angular/core": ">=20.3.25", - "@angular/forms": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/compiler": ">=20.3.27", + "@angular/core": ">=20.3.27", + "@angular/forms": ">=20.3.27", "@angular/material": ">=20.2.14", - "@angular/platform-browser": ">=20.3.25", - "@angular/platform-browser-dynamic": ">=20.3.25", - "@angular/router": ">=20.3.25", + "@angular/platform-browser": ">=20.3.27", + "@angular/platform-browser-dynamic": ">=20.3.27", + "@angular/router": ">=20.3.27", "@alfresco/js-api": ">=10.0.0", "@alfresco/adf-core": ">=9.0.0", "@alfresco/adf-content-services": ">=9.0.0", diff --git a/lib/process-services/package.json b/lib/process-services/package.json index 17fc12b3fe..19125a69b4 100644 --- a/lib/process-services/package.json +++ b/lib/process-services/package.json @@ -12,14 +12,14 @@ }, "peerDependencies": { "@angular/cdk": ">=20.2.14", - "@angular/common": ">=20.3.25", - "@angular/compiler": ">=20.3.25", - "@angular/core": ">=20.3.25", - "@angular/forms": ">=20.3.25", + "@angular/common": ">=20.3.27", + "@angular/compiler": ">=20.3.27", + "@angular/core": ">=20.3.27", + "@angular/forms": ">=20.3.27", "@angular/material": ">=20.2.14", - "@angular/platform-browser": ">=20.3.25", - "@angular/platform-browser-dynamic": ">=20.3.25", - "@angular/router": ">=20.3.25", + "@angular/platform-browser": ">=20.3.27", + "@angular/platform-browser-dynamic": ">=20.3.27", + "@angular/router": ">=20.3.27", "@alfresco/js-api": ">=10.0.0", "@alfresco/adf-core": ">=9.0.0", "@alfresco/adf-content-services": ">=9.0.0", diff --git a/package.json b/package.json index f28c8eba5f..ccd6a23b36 100644 --- a/package.json +++ b/package.json @@ -40,17 +40,17 @@ ], "dependencies": { "@angular-eslint/utils": "20.7.0", - "@angular/animations": "20.3.26", + "@angular/animations": "20.3.27", "@angular/cdk": "20.2.14", "@angular/common": "20.3.27", "@angular/compiler": "20.3.27", "@angular/core": "20.3.27", - "@angular/forms": "20.3.26", + "@angular/forms": "20.3.27", "@angular/material": "20.2.14", "@angular/material-date-fns-adapter": "20.2.14", - "@angular/platform-browser": "20.3.26", - "@angular/platform-browser-dynamic": "20.3.26", - "@angular/router": "20.3.26", + "@angular/platform-browser": "20.3.27", + "@angular/platform-browser-dynamic": "20.3.27", + "@angular/router": "20.3.27", "@apollo/client": "3.13.1", "@cspell/eslint-plugin": "10.0.0", "@mat-datetimepicker/core": "16.0.1", @@ -83,7 +83,7 @@ "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/template-parser": "22.1.0", "@angular/build": "20.3.32", - "@angular/compiler-cli": "20.3.26", + "@angular/compiler-cli": "20.3.27", "@chromatic-com/storybook": "4.1.3", "@eslint/compat": "^2.1.0", "@nx/angular": "23.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 089c200cbf..c4e5d9addf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,8 +32,8 @@ importers: specifier: 20.7.0 version: 20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) '@angular/animations': - specifier: 20.3.26 - version: 20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + specifier: 20.3.27 + version: 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) '@angular/cdk': specifier: 20.2.14 version: 20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) @@ -47,23 +47,23 @@ importers: specifier: 20.3.27 version: 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) '@angular/forms': - specifier: 20.3.26 - version: 20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) + specifier: 20.3.27 + version: 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) '@angular/material': specifier: 20.2.14 - version: 20.2.14(455430e5d13335858a88189cadfe3159) + version: 20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7) '@angular/material-date-fns-adapter': specifier: 20.2.14 - version: 20.2.14(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(455430e5d13335858a88189cadfe3159))(date-fns@2.30.0) + version: 20.2.14(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7))(date-fns@2.30.0) '@angular/platform-browser': - specifier: 20.3.26 - version: 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + specifier: 20.3.27 + version: 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) '@angular/platform-browser-dynamic': - specifier: 20.3.26 - version: 20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))) + specifier: 20.3.27 + version: 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))) '@angular/router': - specifier: 20.3.26 - version: 20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) + specifier: 20.3.27 + version: 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) '@apollo/client': specifier: 3.13.1 version: 3.13.1(graphql-ws@6.0.8(graphql@16.14.0)(ws@8.21.0))(graphql@16.14.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -72,7 +72,7 @@ importers: version: 10.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) '@mat-datetimepicker/core': specifier: 16.0.1 - version: 16.0.1(@angular/cdk@20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(455430e5d13335858a88189cadfe3159)) + version: 16.0.1(@angular/cdk@20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7)) '@ngx-translate/core': specifier: 17.0.0 version: 17.0.0(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) @@ -154,10 +154,10 @@ importers: version: 22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) '@angular/build': specifier: 20.3.32 - version: 20.3.32(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) + version: 20.3.32(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) '@angular/compiler-cli': - specifier: 20.3.26 - version: 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + specifier: 20.3.27 + version: 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@chromatic-com/storybook': specifier: 4.1.3 version: 4.1.3(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) @@ -166,7 +166,7 @@ importers: version: 2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) '@nx/angular': specifier: 23.1.1 - version: 23.1.1(14b0b041890a4d9ad84851ba43aed6f3) + version: 23.1.1(e4f4bd1e173cc1faf5ba1d5ef56b0a42) '@nx/eslint-plugin': specifier: 23.1.1 version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) @@ -187,7 +187,7 @@ importers: version: 10.4.0(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) '@storybook/angular': specifier: 10.4.0 - version: 10.4.0(641969a558ebac8287ee9e40d24c89b8) + version: 10.4.0(b1e920b800e57ca86dc7f2bb2f6e0689) '@types/jasmine': specifier: 4.0.3 version: 4.0.3 @@ -280,7 +280,7 @@ importers: version: 17.2.0 ng-packagr: specifier: 20.3.2 - version: 20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + version: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) nx: specifier: 23.1.1 version: 23.1.1 @@ -463,12 +463,12 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular/animations@20.3.26': - resolution: {integrity: sha512-hfNrX19v8xs/usNkELSqc6q5IwBfS2GGW8sQ4OMpxAmLZDJwaLUxkj48t8VYhUFezsIfzR+sDEi6ZQBOaMIYug==} + '@angular/animations@20.3.27': + resolution: {integrity: sha512-BgGTloDiD3qIFVSxZq8xO6CiyhKn00WbhQQiklZF8WI2hXd3Hmc1OUAAHqSMh2c9uL7X1ZYkg9lSzjiasK2vKg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} deprecated: '@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.' peerDependencies: - '@angular/core': 20.3.26 + '@angular/core': 20.3.27 '@angular/build@20.3.16': resolution: {integrity: sha512-p1W3wwMG1Bs4tkPW7ceXO4woO1KCP28sjfpBJg32dIMW3dYSC+iWNmUkYS/wb4YEkqCV0wd6Apnd98mZjL6rNg==} @@ -576,12 +576,12 @@ packages: '@angular/core': 20.3.27 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@20.3.26': - resolution: {integrity: sha512-3rHtC87ecldvaiFHwQEZ6Wx3QaZ/Q7b0Gb7XORDOjn/M+5CYZ4rQsvbxE5TUjwreg07oQ4Y2h8ADESXTJEUYOQ==} + '@angular/compiler-cli@20.3.27': + resolution: {integrity: sha512-R0j9mFfUdGmmw867V/TfMSOBkLZT6ASxyY5tc1NDNmxQioZDVIDP9pqBOayzhJ0xiuDc9JellQXUTZ+vm+b/Zg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 20.3.26 + '@angular/compiler': 20.3.27 typescript: '>=5.8 <6.0' peerDependenciesMeta: typescript: @@ -604,13 +604,13 @@ packages: zone.js: optional: true - '@angular/forms@20.3.26': - resolution: {integrity: sha512-ia0YaPVjlG2oBFKCfaAgqQ0jGRrhGTAcrbZG3tVeFDpi7LQ6WdP3Syw2H+0D3GPyzpl/5UqU10Fum5Wr1br4QQ==} + '@angular/forms@20.3.27': + resolution: {integrity: sha512-cNG26wi3tr3m8At6puxJpAMVk9mBhEqREA4Jk/klalMwWuYEf8ApTEAw0a5NUfMxDkL3dcJJwDRf8rK6ma6TYQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 20.3.26 - '@angular/core': 20.3.26 - '@angular/platform-browser': 20.3.26 + '@angular/common': 20.3.27 + '@angular/core': 20.3.27 + '@angular/platform-browser': 20.3.27 rxjs: ^6.5.3 || ^7.4.0 '@angular/material-date-fns-adapter@20.2.14': @@ -630,34 +630,34 @@ packages: '@angular/platform-browser': ^20.0.0 || ^21.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/platform-browser-dynamic@20.3.26': - resolution: {integrity: sha512-/9eq0GGmtMoBV5UvcjvhnV4ZDcgZr15h+dzoxkZodUncb2z7fxybSyha7wWD9aTeabZ/q/KYVUSnoYqVyBhbVQ==} + '@angular/platform-browser-dynamic@20.3.27': + resolution: {integrity: sha512-4UUs8vOswgBOWCRoeZrswguarBLrM3j6WGb927Y3GXdN37fVJQYjyKHivNeQwZvwsGgl5Nl6mvpBpZv47n/OrQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} deprecated: '@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.' peerDependencies: - '@angular/common': 20.3.26 - '@angular/compiler': 20.3.26 - '@angular/core': 20.3.26 - '@angular/platform-browser': 20.3.26 + '@angular/common': 20.3.27 + '@angular/compiler': 20.3.27 + '@angular/core': 20.3.27 + '@angular/platform-browser': 20.3.27 - '@angular/platform-browser@20.3.26': - resolution: {integrity: sha512-In4wUiLUUT9LqyV9Rjz78k/dsnKAwec4AtDmwZoX8/ZmeJOSH7g5X1gTM+hxTxmifmZkrapQjXR299IcfIkzrw==} + '@angular/platform-browser@20.3.27': + resolution: {integrity: sha512-IV2zQ4zk6liyw5NE48bQqSk3nOAZ1rmDAQi7W4Kw0N8cs9MYVgK3zulo0zx5UqSv1kuNDAGb0HPR5tUGVOf9kw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 20.3.26 - '@angular/common': 20.3.26 - '@angular/core': 20.3.26 + '@angular/animations': 20.3.27 + '@angular/common': 20.3.27 + '@angular/core': 20.3.27 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@20.3.26': - resolution: {integrity: sha512-q0k0b5uuQx93Trk4qEMYe8LoPOozheRBIjze51q+LUTlLXWik4W0ughXLiTJL346KRudR/vB5ksfZP8b6WlQyA==} + '@angular/router@20.3.27': + resolution: {integrity: sha512-F3hfJQ0GAuD6LdeB7A6fMfaErc4HXCtCQGh3C/8VrbVEbGfIgSveNjQXzGYPNklnOLhj1BmWf5w0WniUAEjLBA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 20.3.26 - '@angular/core': 20.3.26 - '@angular/platform-browser': 20.3.26 + '@angular/common': 20.3.27 + '@angular/core': 20.3.27 + '@angular/platform-browser': 20.3.27 rxjs: ^6.5.3 || ^7.4.0 '@apollo/client@3.13.1': @@ -9369,14 +9369,14 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@20.3.16(18d336cd389c837facbd563dfff009d7)': + '@angular-devkit/build-angular@20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.16(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.2003.16(chokidar@4.0.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) '@angular-devkit/core': 20.3.16(chokidar@4.0.3) - '@angular/build': 20.3.16(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/build': 20.3.16(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@babel/core': 7.28.3(supports-color@7.2.0) '@babel/generator': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 @@ -9387,7 +9387,7 @@ snapshots: '@babel/preset-env': 7.28.3(@babel/core@7.28.3(supports-color@7.2.0))(supports-color@7.2.0) '@babel/runtime': 7.28.3 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 20.3.16(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@ngtools/webpack': 20.3.16(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) ansi-colors: 4.1.3 autoprefixer: 10.4.21(postcss@8.5.25) babel-loader: 10.0.0(@babel/core@7.28.3(supports-color@7.2.0))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) @@ -9429,10 +9429,10 @@ snapshots: webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) optionalDependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) esbuild: 0.25.9 karma: 6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) - ng-packagr: 20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + ng-packagr: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) transitivePeerDependencies: - '@angular/compiler' - '@minify-html/node' @@ -9542,17 +9542,17 @@ snapshots: eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 - '@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))': + '@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))': dependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) tslib: 2.8.1 - '@angular/build@20.3.16(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0)': + '@angular/build@20.3.16(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.16(chokidar@4.0.3) '@angular/compiler': 20.3.27 - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@babel/core': 7.28.3(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 @@ -9581,11 +9581,11 @@ snapshots: watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) karma: 6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) less: 4.4.0 lmdb: 3.4.2 - ng-packagr: 20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + ng-packagr: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.25 transitivePeerDependencies: - '@types/node' @@ -9600,12 +9600,12 @@ snapshots: - tsx - yaml - '@angular/build@20.3.32(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0)': + '@angular/build@20.3.32(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.32(chokidar@4.0.3) '@angular/compiler': 20.3.27 - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 @@ -9634,11 +9634,11 @@ snapshots: watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) karma: 6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) less: 4.6.6 lmdb: 3.4.2 - ng-packagr: 20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + ng-packagr: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: 8.5.25 transitivePeerDependencies: - '@types/node' @@ -9667,7 +9667,7 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3)': + '@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@angular/compiler': 20.3.27 '@babel/core': 7.29.7(supports-color@7.2.0) @@ -9695,52 +9695,52 @@ snapshots: '@angular/compiler': 20.3.27 zone.js: 0.15.0 - '@angular/forms@20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2)': + '@angular/forms@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2)': dependencies: '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/material-date-fns-adapter@20.2.14(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(455430e5d13335858a88189cadfe3159))(date-fns@2.30.0)': + '@angular/material-date-fns-adapter@20.2.14(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7))(date-fns@2.30.0)': dependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/material': 20.2.14(455430e5d13335858a88189cadfe3159) + '@angular/material': 20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7) date-fns: 2.30.0 tslib: 2.8.1 - '@angular/material@20.2.14(455430e5d13335858a88189cadfe3159)': + '@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7)': dependencies: '@angular/cdk': 20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/forms': 20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/forms': 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser-dynamic@20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))': + '@angular/platform-browser-dynamic@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))': dependencies: '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/compiler': 20.3.27 '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) tslib: 2.8.1 - '@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))': + '@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))': dependencies: '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) tslib: 2.8.1 optionalDependencies: - '@angular/animations': 20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/animations': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) - '@angular/router@20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2)': + '@angular/router@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(rxjs@7.8.2)': dependencies: '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) rxjs: 7.8.2 tslib: 2.8.1 @@ -12063,12 +12063,12 @@ snapshots: '@lmdb/lmdb-win32-x64@3.4.2': optional: true - '@mat-datetimepicker/core@16.0.1(@angular/cdk@20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(455430e5d13335858a88189cadfe3159))': + '@mat-datetimepicker/core@16.0.1(@angular/cdk@20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7))': dependencies: '@angular/cdk': 20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/material': 20.2.14(455430e5d13335858a88189cadfe3159) + '@angular/material': 20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7) tslib: 2.8.1 '@module-federation/bridge-react-webpack-plugin@2.5.1(node-fetch@2.7.0(encoding@0.1.13))': @@ -12455,9 +12455,9 @@ snapshots: '@neoconfetti/react@1.0.0': {} - '@ngtools/webpack@20.3.16(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': + '@ngtools/webpack@20.3.16(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': dependencies: - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) typescript: 5.9.3 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) @@ -12481,7 +12481,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nx/angular@23.1.1(14b0b041890a4d9ad84851ba43aed6f3)': + '@nx/angular@23.1.1(e4f4bd1e173cc1faf5ba1d5ef56b0a42)': dependencies: '@angular-devkit/core': 20.3.32(chokidar@4.0.3) '@angular-devkit/schematics': 20.3.32(chokidar@4.0.3) @@ -12505,10 +12505,10 @@ snapshots: tslib: 2.8.1 webpack-merge: 5.10.0 optionalDependencies: - '@angular-devkit/build-angular': 20.3.16(18d336cd389c837facbd563dfff009d7) - '@angular/build': 20.3.32(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) + '@angular-devkit/build-angular': 20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0) + '@angular/build': 20.3.32(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) '@nx/cypress': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) - ng-packagr: 20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + ng-packagr: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) transitivePeerDependencies: - '@babel/traverse' - '@minify-html/node' @@ -13663,17 +13663,17 @@ snapshots: storybook: 10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) ts-dedent: 2.3.0 - '@storybook/angular@10.4.0(641969a558ebac8287ee9e40d24c89b8)': + '@storybook/angular@10.4.0(b1e920b800e57ca86dc7f2bb2f6e0689)': dependencies: '@angular-devkit/architect': 0.2003.16(chokidar@4.0.3) - '@angular-devkit/build-angular': 20.3.16(18d336cd389c837facbd563dfff009d7) + '@angular-devkit/build-angular': 20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0) '@angular-devkit/core': 20.3.32(chokidar@4.0.3) '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/compiler': 20.3.27 - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) - '@angular/platform-browser': 20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) - '@angular/platform-browser-dynamic': 20.3.26(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.26(@angular/animations@20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))) + '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/platform-browser-dynamic': 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))) '@storybook/builder-webpack5': 10.4.0(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@5.9.3) '@storybook/global': 5.0.0 rxjs: 7.8.2 @@ -13684,7 +13684,7 @@ snapshots: typescript: 5.9.3 webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: - '@angular/animations': 20.3.26(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) + '@angular/animations': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) zone.js: 0.15.0 transitivePeerDependencies: - '@minify-html/node' @@ -17239,10 +17239,10 @@ snapshots: neo-async@2.6.2: {} - ng-packagr@20.3.2(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): + ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): dependencies: '@ampproject/remapping': 2.3.0 - '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) + '@angular/compiler-cli': 20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3) '@rollup/plugin-json': 6.1.0(rollup@4.62.2) '@rollup/wasm-node': 4.62.2 ajv: 8.20.0 From 5feb6d89ecf6987e5171527e234fa850730afff5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:55:07 +0200 Subject: [PATCH 04/31] build(deps-dev): bump the typescript-eslint group across 1 directory with 4 updates (#12173) Bumps the typescript-eslint group with 4 updates in the / directory: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@typescript-eslint/typescript-estree](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-estree) and [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils). Updates `@typescript-eslint/eslint-plugin` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/parser) Updates `@typescript-eslint/typescript-estree` from 8.66.0 to 8.67.0 - [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.67.0/packages/typescript-estree) Updates `@typescript-eslint/utils` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/utils) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: typescript-eslint - dependency-name: "@typescript-eslint/parser" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: typescript-eslint - dependency-name: "@typescript-eslint/typescript-estree" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: typescript-eslint - dependency-name: "@typescript-eslint/utils" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: typescript-eslint ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 8 +- pnpm-lock.yaml | 193 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 145 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index ccd6a23b36..57a04952ef 100644 --- a/package.json +++ b/package.json @@ -98,10 +98,10 @@ "@types/jasminewd2": "2.0.13", "@types/node": "26.1.1", "@types/sinon": "22.0.0", - "@typescript-eslint/eslint-plugin": "8.66.0", - "@typescript-eslint/parser": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", "ajv": "8.20.0", "dotenv": "16.4.7", "eslint": "10.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4e5d9addf..9fed5888c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,7 @@ importers: dependencies: '@angular-eslint/utils': specifier: 20.7.0 - version: 20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) + version: 20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) '@angular/animations': specifier: 20.3.27 version: 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) @@ -145,10 +145,10 @@ importers: version: 20.3.32(chokidar@4.0.3) '@angular-eslint/eslint-plugin': specifier: 20.7.0 - version: 20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) + version: 20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) '@angular-eslint/eslint-plugin-template': specifier: 20.7.0 - version: 20.7.0(@angular-eslint/template-parser@22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3))(@typescript-eslint/types@8.65.0)(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) + version: 20.7.0(@angular-eslint/template-parser@22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3))(@typescript-eslint/types@8.67.0)(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) '@angular-eslint/template-parser': specifier: 22.1.0 version: 22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) @@ -169,7 +169,7 @@ importers: version: 23.1.1(e4f4bd1e173cc1faf5ba1d5ef56b0a42) '@nx/eslint-plugin': specifier: 23.1.1 - version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) + version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) '@nx/js': specifier: 23.1.1 version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) @@ -201,17 +201,17 @@ importers: specifier: 22.0.0 version: 22.0.0 '@typescript-eslint/eslint-plugin': - specifier: 8.66.0 - version: 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + specifier: 8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.66.0 - version: 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + specifier: 8.67.0 + version: 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/typescript-estree': - specifier: 8.66.0 - version: 8.66.0(supports-color@7.2.0)(typescript@5.9.3) + specifier: 8.67.0 + version: 8.67.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/utils': - specifier: 8.66.0 - version: 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + specifier: 8.67.0 + version: 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) ajv: specifier: 8.20.0 version: 8.20.0 @@ -4338,11 +4338,11 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.66.0': - resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} + '@typescript-eslint/eslint-plugin@8.67.0': + resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.66.0 + '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' @@ -4352,8 +4352,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@typescript-eslint/parser@8.66.0': - resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} + '@typescript-eslint/parser@8.67.0': + resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4371,6 +4371,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4383,6 +4389,10 @@ packages: resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.65.0': resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4395,6 +4405,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.65.0': resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4402,8 +4418,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.66.0': - resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} + '@typescript-eslint/type-utils@8.67.0': + resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4421,6 +4437,10 @@ packages: resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4442,6 +4462,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@5.62.0': resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4462,6 +4488,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4474,6 +4507,10 @@ packages: resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.2': resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} @@ -4562,6 +4599,7 @@ packages: '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -9507,23 +9545,23 @@ snapshots: '@angular-eslint/bundled-angular-compiler@22.1.0': {} - '@angular-eslint/eslint-plugin-template@20.7.0(@angular-eslint/template-parser@22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3))(@typescript-eslint/types@8.65.0)(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': + '@angular-eslint/eslint-plugin-template@20.7.0(@angular-eslint/template-parser@22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3))(@typescript-eslint/types@8.67.0)(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 '@angular-eslint/template-parser': 22.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) - '@angular-eslint/utils': 20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@angular-eslint/utils': 20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) aria-query: 5.3.2 axobject-query: 4.1.0 eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 - '@angular-eslint/eslint-plugin@20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': + '@angular-eslint/eslint-plugin@20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 - '@angular-eslint/utils': 20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@angular-eslint/utils': 20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 @@ -9535,10 +9573,10 @@ snapshots: eslint-scope: 9.1.2 typescript: 5.9.3 - '@angular-eslint/utils@20.7.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': + '@angular-eslint/utils@20.7.0(@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 @@ -11434,7 +11472,7 @@ snapshots: '@es-joy/jsdoccomment@0.90.0': dependencies: '@types/estree': 1.0.9 - '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/types': 8.67.0 comment-parser: 1.4.7 esquery: 1.7.0 jsdoc-type-pratt-parser: 7.3.0 @@ -12584,13 +12622,13 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/eslint-plugin@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)': + '@nx/eslint-plugin@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@nx/devkit': 23.1.1(nx@23.1.1) '@nx/js': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) '@phenomnomnominal/tsquery': 6.2.0(typescript@5.9.3) '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) confusing-browser-globals: 1.0.11 globals: 17.6.0 jsonc-eslint-parser: 2.4.2 @@ -12598,7 +12636,7 @@ snapshots: semver: 7.6.3 tslib: 2.8.1 optionalDependencies: - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) eslint-config-prettier: 10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) transitivePeerDependencies: - '@babel/traverse' @@ -13974,14 +14012,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/parser': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.67.0 eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 @@ -13998,12 +14036,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@7.2.0) eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 @@ -14012,8 +14050,8 @@ snapshots: '@typescript-eslint/project-service@8.65.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) - '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: @@ -14021,8 +14059,17 @@ snapshots: '@typescript-eslint/project-service@8.66.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) - '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: @@ -14043,6 +14090,11 @@ snapshots: '@typescript-eslint/types': 8.66.0 '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/scope-manager@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 @@ -14051,6 +14103,10 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.65.0 @@ -14063,11 +14119,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) debug: 4.4.3(supports-color@7.2.0) eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -14081,6 +14137,8 @@ snapshots: '@typescript-eslint/types@8.66.0': {} + '@typescript-eslint/types@8.67.0': {} + '@typescript-eslint/typescript-estree@5.62.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -14125,6 +14183,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@5.62.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) @@ -14162,6 +14235,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) + eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -14177,6 +14261,11 @@ snapshots: '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.2': {} '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@26.1.1)(jiti@2.7.0)(less@4.4.0)(sass-embedded@1.100.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.9.0))': @@ -15751,7 +15840,7 @@ snapshots: eslint-plugin-storybook@10.4.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(supports-color@7.2.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) storybook: 10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) transitivePeerDependencies: From c032051b68c1fe66d2e7ac614d33ee89e10d4688 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:55:35 +0200 Subject: [PATCH 05/31] build(deps-dev): bump webpack from 5.109.0 to 5.109.2 (#12174) Bumps [webpack](https://github.com/webpack/webpack) from 5.109.0 to 5.109.2. - [Release notes](https://github.com/webpack/webpack/releases) - [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack/compare/v5.109.0...v5.109.2) --- updated-dependencies: - dependency-name: webpack dependency-version: 5.109.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 418 ++++++++++++++++++++++++++----------------------- 2 files changed, 227 insertions(+), 193 deletions(-) diff --git a/package.json b/package.json index 57a04952ef..5ed0791e5e 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "ts-node": "10.9.2", "typescript": "5.9.3", "undici": "8.9.0", - "webpack": "5.109.0" + "webpack": "5.109.2" }, "license": "Apache-2.0", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fed5888c6..761e4f0a1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,7 +166,7 @@ importers: version: 2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) '@nx/angular': specifier: 23.1.1 - version: 23.1.1(e4f4bd1e173cc1faf5ba1d5ef56b0a42) + version: 23.1.1(1871ac9779a3c1c87e7a2019dffef8ff) '@nx/eslint-plugin': specifier: 23.1.1 version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@typescript-eslint/parser@8.67.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) @@ -175,7 +175,7 @@ importers: version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) '@nx/storybook': specifier: 23.1.1 - version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@nx/web@23.1.1(69165f71226cdd786def4faf955c6def))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(supports-color@7.2.0)(typescript@5.9.3) + version: 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@nx/web@23.1.1(9aad18717af1cde75effe0fe4ceb25af))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(supports-color@7.2.0)(typescript@5.9.3) '@nx/workspace': specifier: 23.1.1 version: 23.1.1 @@ -187,7 +187,7 @@ importers: version: 10.4.0(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) '@storybook/angular': specifier: 10.4.0 - version: 10.4.0(b1e920b800e57ca86dc7f2bb2f6e0689) + version: 10.4.0(69b0d8b90b0fe3d6016d76a1b6c3fcbb) '@types/jasmine': specifier: 4.0.3 version: 4.0.3 @@ -298,7 +298,7 @@ importers: version: 6.1.3 sass-loader: specifier: 16.0.8 - version: 16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + version: 16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) semver: specifier: 7.6.3 version: 7.6.3 @@ -327,8 +327,8 @@ importers: specifier: 8.9.0 version: 8.9.0 webpack: - specifier: 5.109.0 - version: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + specifier: 5.109.2 + version: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) lib/eslint-angular: {} @@ -4281,9 +4281,6 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} - '@types/node@26.1.1': resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} @@ -4642,6 +4639,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + address@2.0.3: resolution: {integrity: sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==} engines: {node: '>= 16.0.0'} @@ -4901,6 +4903,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + baseline-browser-mapping@2.11.14: + resolution: {integrity: sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==} + engines: {node: '>=6.0.0'} + hasBin: true + basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} @@ -4960,6 +4967,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5012,6 +5024,9 @@ packages: caniuse-lite@1.0.30001803: resolution: {integrity: sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} engines: {node: '>=4'} @@ -5655,6 +5670,9 @@ packages: electron-to-chromium@1.5.389: resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} + electron-to-chromium@1.5.406: + resolution: {integrity: sha512-hWH5ORBi3d0IipnMh7BN5GDTaAmrSSSWmznwt2zltdiRNEWoEQyTwF0FFSBxzHO7hLSRT6loQu3IQGV0wg/Tvg==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -5687,12 +5705,8 @@ packages: resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.24.0: - resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} - engines: {node: '>=10.13.0'} - - enhanced-resolve@5.24.3: - resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} enquirer@2.3.6: @@ -5755,8 +5769,8 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -7277,6 +7291,10 @@ packages: resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + node-schedule@2.1.1: resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} engines: {node: '>=6'} @@ -8742,8 +8760,8 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.49.0: - resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} + terser@5.50.0: + resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} engines: {node: '>=10'} hasBin: true @@ -8927,9 +8945,6 @@ packages: resolution: {integrity: sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==} hasBin: true - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -8979,6 +8994,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -9225,8 +9246,8 @@ packages: webpack-cli: optional: true - webpack@5.109.0: - resolution: {integrity: sha512-vomrngskVVXEZF9sMZfYAd4pXZUnfaWdJGlF+BTNF+gJBCKYCQBnOeVPlrh39Ewl7nlCsirDplMy6o5g9xJHBg==} + webpack@5.109.2: + resolution: {integrity: sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -9407,7 +9428,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0)': + '@angular-devkit/build-angular@20.3.16(c48ca78b9b10b3f91f8dcbb193e451e5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.16(chokidar@4.0.3) @@ -9429,7 +9450,7 @@ snapshots: ansi-colors: 4.1.3 autoprefixer: 10.4.21(postcss@8.5.25) babel-loader: 10.0.0(@babel/core@7.28.3(supports-color@7.2.0))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - browserslist: 4.28.5 + browserslist: 4.28.8 copy-webpack-plugin: 13.0.1(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) css-loader: 7.1.2(@rspack/core@1.6.8)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) esbuild-wasm: 0.25.9 @@ -9464,7 +9485,7 @@ snapshots: webpack-dev-middleware: 7.4.2(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) webpack-dev-server: 6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) optionalDependencies: '@angular/core': 20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0) '@angular/platform-browser': 20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) @@ -9597,7 +9618,7 @@ snapshots: '@inquirer/confirm': 5.1.14(@types/node@26.1.1) '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@26.1.1)(jiti@2.7.0)(less@4.4.0)(sass-embedded@1.100.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.9.0)) beasties: 0.3.5 - browserslist: 4.28.5 + browserslist: 4.28.8 esbuild: 0.25.9 https-proxy-agent: 7.0.6(supports-color@7.2.0) istanbul-lib-instrument: 6.0.3(supports-color@7.2.0) @@ -9881,7 +9902,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.5 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 @@ -12151,7 +12172,7 @@ snapshots: - utf-8-validate optional: true - '@module-federation/enhanced@2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': + '@module-federation/enhanced@2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': dependencies: '@module-federation/bridge-react-webpack-plugin': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/cli': 2.5.1(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) @@ -12169,7 +12190,7 @@ snapshots: upath: 2.0.1 optionalDependencies: typescript: 5.9.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -12210,16 +12231,16 @@ snapshots: - vue-tsc optional: true - '@module-federation/node@2.7.44(@rspack/core@1.6.8)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': + '@module-federation/node@2.7.44(@rspack/core@1.6.8)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': dependencies: - '@module-federation/enhanced': 2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@module-federation/enhanced': 2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) '@module-federation/runtime': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/sdk': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) encoding: 0.1.13 node-fetch: 2.7.0(encoding@0.1.13) tapable: 2.3.0 optionalDependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -12519,17 +12540,17 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nx/angular@23.1.1(e4f4bd1e173cc1faf5ba1d5ef56b0a42)': + '@nx/angular@23.1.1(1871ac9779a3c1c87e7a2019dffef8ff)': dependencies: '@angular-devkit/core': 20.3.32(chokidar@4.0.3) '@angular-devkit/schematics': 20.3.32(chokidar@4.0.3) '@nx/devkit': 23.1.1(nx@23.1.1) '@nx/eslint': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) '@nx/js': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) - '@nx/module-federation': 23.1.1(159f6bdc10a59ab922b88ca0434a74ba) - '@nx/rspack': 23.1.1(d1f2db652c3431e28213a9ed1561260a) - '@nx/web': 23.1.1(69165f71226cdd786def4faf955c6def) - '@nx/webpack': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@nx/module-federation': 23.1.1(fbabc342ac83f23db68b8d27ddbe599a) + '@nx/rspack': 23.1.1(f7d730e8feeef54478a80edef0d832f6) + '@nx/web': 23.1.1(9aad18717af1cde75effe0fe4ceb25af) + '@nx/webpack': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) '@phenomnomnominal/tsquery': 6.2.0(typescript@5.9.3) '@schematics/angular': 20.3.32(chokidar@4.0.3) '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) @@ -12543,7 +12564,7 @@ snapshots: tslib: 2.8.1 webpack-merge: 5.10.0 optionalDependencies: - '@angular-devkit/build-angular': 20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0) + '@angular-devkit/build-angular': 20.3.16(c48ca78b9b10b3f91f8dcbb193e451e5) '@angular/build': 20.3.32(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.27(@angular/animations@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@26.1.1)(chokidar@4.0.3)(jiti@2.7.0)(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0))(less@4.6.6)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.25)(sass-embedded@1.100.0)(supports-color@7.2.0)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.9.0) '@nx/cypress': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) ng-packagr: 20.3.2(@angular/compiler-cli@20.3.27(@angular/compiler@20.3.27)(supports-color@7.2.0)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) @@ -12706,11 +12727,11 @@ snapshots: - nx - supports-color - '@nx/module-federation@23.1.1(159f6bdc10a59ab922b88ca0434a74ba)': + '@nx/module-federation@23.1.1(fbabc342ac83f23db68b8d27ddbe599a)': dependencies: '@nx/devkit': 23.1.1(nx@23.1.1) '@nx/js': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) - '@nx/web': 23.1.1(69165f71226cdd786def4faf955c6def) + '@nx/web': 23.1.1(9aad18717af1cde75effe0fe4ceb25af) '@rspack/core': 2.1.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13))) express: 4.22.2(supports-color@7.2.0) http-proxy-middleware: 3.0.7(supports-color@7.2.0) @@ -12718,8 +12739,8 @@ snapshots: tslib: 2.8.1 webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: - '@module-federation/enhanced': 2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - '@module-federation/node': 2.7.44(@rspack/core@1.6.8)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@module-federation/enhanced': 2.5.1(@rspack/core@1.6.8)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@module-federation/node': 2.7.44(@rspack/core@1.6.8)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) transitivePeerDependencies: - '@babel/traverse' - '@minify-html/node' @@ -12780,15 +12801,15 @@ snapshots: '@nx/nx-win32-x64-msvc@23.1.1': optional: true - '@nx/rspack@23.1.1(d1f2db652c3431e28213a9ed1561260a)': + '@nx/rspack@23.1.1(f7d730e8feeef54478a80edef0d832f6)': dependencies: '@nx/devkit': 23.1.1(nx@23.1.1) '@nx/js': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) - '@nx/module-federation': 23.1.1(159f6bdc10a59ab922b88ca0434a74ba) - '@nx/web': 23.1.1(69165f71226cdd786def4faf955c6def) + '@nx/module-federation': 23.1.1(fbabc342ac83f23db68b8d27ddbe599a) + '@nx/web': 23.1.1(9aad18717af1cde75effe0fe4ceb25af) '@phenomnomnominal/tsquery': 6.2.0(typescript@5.9.3) autoprefixer: 10.5.0(postcss@8.5.25) - browserslist: 4.28.5 + browserslist: 4.28.8 css-loader: 6.11.0(@rspack/core@1.6.8)(webpack@5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) enquirer: 2.3.6 express: 4.22.2(supports-color@7.2.0) @@ -12813,7 +12834,7 @@ snapshots: webpack-node-externals: 3.0.0 optionalDependencies: '@rspack/core': 1.6.8 - '@rspack/dev-server': 1.2.1(@rspack/core@1.6.8)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@rspack/dev-server': 1.2.1(@rspack/core@1.6.8)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) '@rspack/plugin-react-refresh': 1.6.2(react-refresh@0.18.0)(webpack-hot-middleware@2.26.1) transitivePeerDependencies: - '@babel/traverse' @@ -12850,7 +12871,7 @@ snapshots: - verdaccio - webpack-cli - '@nx/storybook@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@nx/web@23.1.1(69165f71226cdd786def4faf955c6def))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(supports-color@7.2.0)(typescript@5.9.3)': + '@nx/storybook@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@nx/web@23.1.1(9aad18717af1cde75effe0fe4ceb25af))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@nx/cypress': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) '@nx/devkit': 23.1.1(nx@23.1.1) @@ -12861,7 +12882,7 @@ snapshots: storybook: 10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tslib: 2.8.1 optionalDependencies: - '@nx/web': 23.1.1(69165f71226cdd786def4faf955c6def) + '@nx/web': 23.1.1(9aad18717af1cde75effe0fe4ceb25af) transitivePeerDependencies: - '@babel/traverse' - '@nx/jest' @@ -12876,7 +12897,7 @@ snapshots: - typescript - verdaccio - '@nx/web@23.1.1(69165f71226cdd786def4faf955c6def)': + '@nx/web@23.1.1(9aad18717af1cde75effe0fe4ceb25af)': dependencies: '@nx/devkit': 23.1.1(nx@23.1.1) '@nx/js': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) @@ -12887,7 +12908,7 @@ snapshots: optionalDependencies: '@nx/cypress': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3) '@nx/eslint': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@zkochan/js-yaml@0.0.7)(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(nx@23.1.1)(supports-color@7.2.0) - '@nx/webpack': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + '@nx/webpack': 23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -12898,7 +12919,7 @@ snapshots: - supports-color - verdaccio - '@nx/webpack@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': + '@nx/webpack@23.1.1(@babel/traverse@7.29.7(supports-color@7.2.0))(@rspack/core@1.6.8)(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(nx@23.1.1)(supports-color@7.2.0)(typescript@5.9.3)(webpack-dev-server@6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@nx/devkit': 23.1.1(nx@23.1.1) @@ -12906,36 +12927,36 @@ snapshots: '@phenomnomnominal/tsquery': 6.2.0(typescript@5.9.3) ajv: 8.20.0 autoprefixer: 10.5.0(postcss@8.5.25) - babel-loader: 9.2.1(@babel/core@7.29.7(supports-color@7.2.0))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - browserslist: 4.28.5 - copy-webpack-plugin: 14.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - css-loader: 6.11.0(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - css-minimizer-webpack-plugin: 8.0.0(clean-css@5.3.3)(csso@5.0.5)(esbuild@0.25.9)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + babel-loader: 9.2.1(@babel/core@7.29.7(supports-color@7.2.0))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + browserslist: 4.28.8 + copy-webpack-plugin: 14.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + css-loader: 6.11.0(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + css-minimizer-webpack-plugin: 8.0.0(clean-css@5.3.3)(csso@5.0.5)(esbuild@0.25.9)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) less: 4.5.1 - less-loader: 12.3.3(@rspack/core@1.6.8)(less@4.5.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - license-webpack-plugin: 4.0.2(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + less-loader: 12.3.3(@rspack/core@1.6.8)(less@4.5.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + license-webpack-plugin: 4.0.2(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) loader-utils: 2.0.4 - mini-css-extract-plugin: 2.4.7(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + mini-css-extract-plugin: 2.4.7(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) parse5: 4.0.0 picocolors: 1.1.1 postcss: 8.5.25 postcss-import: 14.1.0(postcss@8.5.25) - postcss-loader: 8.2.1(@rspack/core@1.6.8)(postcss@8.5.25)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + postcss-loader: 8.2.1(@rspack/core@1.6.8)(postcss@8.5.25)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) rxjs: 7.8.2 sass: 1.101.0 sass-embedded: 1.100.0 - sass-loader: 16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - source-map-loader: 5.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - style-loader: 3.3.4(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - terser-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - ts-loader: 9.6.1(loader-utils@2.0.4)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + sass-loader: 16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + source-map-loader: 5.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + style-loader: 3.3.4(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + terser-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + ts-loader: 9.6.1(loader-utils@2.0.4)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) tsconfig-paths-webpack-plugin: 4.2.0 tslib: 2.8.1 webpack-node-externals: 3.0.0 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) optionalDependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) webpack-dev-server: 6.0.0(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) transitivePeerDependencies: - '@babel/traverse' @@ -13616,7 +13637,7 @@ snapshots: optionalDependencies: '@module-federation/runtime-tools': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) - '@rspack/dev-server@1.2.1(@rspack/core@1.6.8)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': + '@rspack/dev-server@1.2.1(@rspack/core@1.6.8)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(tslib@2.8.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25))': dependencies: '@rspack/core': 1.6.8 '@types/bonjour': 3.5.13 @@ -13645,7 +13666,7 @@ snapshots: serve-index: 1.9.2(supports-color@7.2.0) sockjs: 0.3.24 spdy: 4.0.2(supports-color@7.2.0) - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -13701,10 +13722,10 @@ snapshots: storybook: 10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) ts-dedent: 2.3.0 - '@storybook/angular@10.4.0(b1e920b800e57ca86dc7f2bb2f6e0689)': + '@storybook/angular@10.4.0(69b0d8b90b0fe3d6016d76a1b6c3fcbb)': dependencies: '@angular-devkit/architect': 0.2003.16(chokidar@4.0.3) - '@angular-devkit/build-angular': 20.3.16(20bdf76cdfbd86a8e9e2bd508d7d40a0) + '@angular-devkit/build-angular': 20.3.16(c48ca78b9b10b3f91f8dcbb193e451e5) '@angular-devkit/core': 20.3.32(chokidar@4.0.3) '@angular/common': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2) '@angular/compiler': 20.3.27 @@ -13720,7 +13741,7 @@ snapshots: ts-dedent: 2.3.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: '@angular/animations': 20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0)) zone.js: 0.15.0 @@ -13745,17 +13766,17 @@ snapshots: '@storybook/core-webpack': 10.4.0(storybook@10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 - css-loader: 7.1.4(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + css-loader: 7.1.4(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) magic-string: 0.30.21 storybook: 10.4.0(@testing-library/dom@10.4.1)(prettier@3.9.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - style-loader: 4.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - terser-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + style-loader: 4.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + terser-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) ts-dedent: 2.3.0 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - webpack-dev-middleware: 6.1.3(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack-dev-middleware: 6.1.3(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -13943,10 +13964,6 @@ snapshots: '@types/node': 26.1.1 optional: true - '@types/node@25.9.1': - dependencies: - undici-types: 7.24.6 - '@types/node@26.1.1': dependencies: undici-types: 8.3.0 @@ -14414,20 +14431,22 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-phases@1.0.4(acorn@8.17.0): + acorn-import-phases@1.0.4(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn-walk@8.3.5: dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn@8.17.0: {} + acorn@8.18.0: {} + address@2.0.3: {} adjust-sourcemap-loader@4.0.0: @@ -14572,8 +14591,8 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.25): dependencies: - browserslist: 4.28.5 - caniuse-lite: 1.0.30001803 + browserslist: 4.28.8 + caniuse-lite: 1.0.30001809 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -14582,7 +14601,7 @@ snapshots: autoprefixer@10.5.0(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 caniuse-lite: 1.0.30001803 fraction.js: 5.3.4 picocolors: 1.1.1 @@ -14607,12 +14626,12 @@ snapshots: find-up: 5.0.0 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - babel-loader@9.2.1(@babel/core@7.29.7(supports-color@7.2.0))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + babel-loader@9.2.1(@babel/core@7.29.7(supports-color@7.2.0))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) babel-plugin-const-enum@1.2.0(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0): dependencies: @@ -14706,6 +14725,8 @@ snapshots: baseline-browser-mapping@2.10.42: {} + baseline-browser-mapping@2.11.14: {} + basic-auth@2.0.1: dependencies: safe-buffer: 5.1.2 @@ -14806,6 +14827,14 @@ snapshots: node-releases: 2.0.51 update-browserslist-db: 1.2.3(browserslist@4.28.5) + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.14 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.406 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + buffer-from@1.1.2: {} buffer@5.7.1: @@ -14852,8 +14881,8 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.28.5 - caniuse-lite: 1.0.30001803 + browserslist: 4.28.8 + caniuse-lite: 1.0.30001809 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -14861,6 +14890,8 @@ snapshots: caniuse-lite@1.0.30001803: {} + caniuse-lite@1.0.30001809: {} + case-sensitive-paths-webpack-plugin@2.4.0: {} caseless@0.12.0: {} @@ -15077,18 +15108,18 @@ snapshots: tinyglobby: 0.2.17 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - copy-webpack-plugin@14.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + copy-webpack-plugin@14.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 7.0.6 tinyglobby: 0.2.17 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) core-js-compat@3.49.0: dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 core-util-is@1.0.3: optional: true @@ -15221,7 +15252,7 @@ snapshots: '@rspack/core': 1.6.8 webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - css-loader@6.11.0(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + css-loader@6.11.0(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: icss-utils: 5.1.0(postcss@8.5.25) postcss: 8.5.25 @@ -15233,7 +15264,7 @@ snapshots: semver: 7.6.3 optionalDependencies: '@rspack/core': 1.6.8 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) css-loader@7.1.2(@rspack/core@1.6.8)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: @@ -15249,7 +15280,7 @@ snapshots: '@rspack/core': 1.6.8 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - css-loader@7.1.4(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + css-loader@7.1.4(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: icss-utils: 5.1.0(postcss@8.5.25) postcss: 8.5.25 @@ -15261,9 +15292,9 @@ snapshots: semver: 7.6.3 optionalDependencies: '@rspack/core': 1.6.8 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - css-minimizer-webpack-plugin@8.0.0(clean-css@5.3.3)(csso@5.0.5)(esbuild@0.25.9)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + css-minimizer-webpack-plugin@8.0.0(clean-css@5.3.3)(csso@5.0.5)(esbuild@0.25.9)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@jridgewell/trace-mapping': 0.3.31 cssnano: 7.1.9(postcss@8.5.25) @@ -15271,7 +15302,7 @@ snapshots: postcss: 8.5.25 schema-utils: 4.3.3 serialize-javascript: 7.0.6 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: clean-css: 5.3.3 csso: 5.0.5 @@ -15321,7 +15352,7 @@ snapshots: cssnano-preset-default@7.0.17(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 css-declaration-sorter: 7.4.0(postcss@8.5.25) cssnano-utils: 5.0.3(postcss@8.5.25) postcss: 8.5.25 @@ -15529,6 +15560,8 @@ snapshots: electron-to-chromium@1.5.389: {} + electron-to-chromium@1.5.406: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -15567,12 +15600,7 @@ snapshots: - supports-color - utf-8-validate - enhanced-resolve@5.24.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - enhanced-resolve@5.24.3: + enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -15626,7 +15654,7 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.1: {} es-object-atoms@1.1.1: dependencies: @@ -15924,14 +15952,14 @@ snapshots: espree@11.2.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 5.0.1 espree@9.6.1: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -16177,7 +16205,7 @@ snapshots: optionalDependencies: debug: 4.4.3(supports-color@7.2.0) - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@babel/code-frame': 7.29.7 chalk: 4.1.2 @@ -16192,7 +16220,7 @@ snapshots: semver: 7.6.3 tapable: 2.3.3 typescript: 5.9.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) form-data@4.0.6: dependencies: @@ -16410,11 +16438,11 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.49.0 + terser: 5.50.0 html-tags@3.3.1: {} - html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -16423,7 +16451,7 @@ snapshots: tapable: 2.3.3 optionalDependencies: '@rspack/core': 1.6.8 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) htmlparser2@10.1.0: dependencies: @@ -16782,7 +16810,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.9.1 + '@types/node': 26.1.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16934,12 +16962,12 @@ snapshots: '@rspack/core': 1.6.8 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - less-loader@12.3.3(@rspack/core@1.6.8)(less@4.5.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + less-loader@12.3.3(@rspack/core@1.6.8)(less@4.5.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: less: 4.5.1 optionalDependencies: '@rspack/core': 1.6.8 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) less-loader@12.3.3(@rspack/core@1.6.8)(less@4.6.6)(webpack@5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: @@ -17006,11 +17034,11 @@ snapshots: optionalDependencies: webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - license-webpack-plugin@4.0.2(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + license-webpack-plugin@4.0.2(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: webpack-sources: 3.5.1 optionalDependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) lilconfig@3.1.3: {} @@ -17233,10 +17261,10 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.4.7(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + mini-css-extract-plugin@2.4.7(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: schema-utils: 4.3.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) mini-css-extract-plugin@2.9.4(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: @@ -17257,13 +17285,13 @@ snapshots: minimist@1.2.8: {} - minimizer-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + minimizer-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + terser: 5.50.0 + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: clean-css: 5.3.3 cssnano: 7.1.9(postcss@8.5.25) @@ -17399,6 +17427,8 @@ snapshots: node-releases@2.0.51: {} + node-releases@2.0.53: {} + node-schedule@2.1.1: dependencies: cron-parser: 4.9.0 @@ -17854,14 +17884,14 @@ snapshots: postcss-colormin@7.0.10(postcss@8.5.25): dependencies: '@colordx/core': 5.4.3 - browserslist: 4.28.5 + browserslist: 4.28.8 caniuse-api: 3.0.0 postcss: 8.5.25 postcss-value-parser: 4.2.0 postcss-convert-values@7.0.12(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 postcss: 8.5.25 postcss-value-parser: 4.2.0 @@ -17913,7 +17943,7 @@ snapshots: transitivePeerDependencies: - typescript - postcss-loader@8.2.1(@rspack/core@1.6.8)(postcss@8.5.25)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + postcss-loader@8.2.1(@rspack/core@1.6.8)(postcss@8.5.25)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: cosmiconfig: 9.0.2(typescript@5.9.3) jiti: 2.7.0 @@ -17921,7 +17951,7 @@ snapshots: semver: 7.6.3 optionalDependencies: '@rspack/core': 1.6.8 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) transitivePeerDependencies: - typescript @@ -17935,7 +17965,7 @@ snapshots: postcss-merge-rules@7.0.11(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 caniuse-api: 3.0.0 cssnano-utils: 5.0.3(postcss@8.5.25) postcss: 8.5.25 @@ -17955,14 +17985,14 @@ snapshots: postcss-minify-params@7.0.9(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 cssnano-utils: 5.0.3(postcss@8.5.25) postcss: 8.5.25 postcss-value-parser: 4.2.0 postcss-minify-selectors@7.1.2(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 caniuse-api: 3.0.0 cssesc: 3.0.0 postcss: 8.5.25 @@ -18020,7 +18050,7 @@ snapshots: postcss-normalize-unicode@7.0.9(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 postcss: 8.5.25 postcss-value-parser: 4.2.0 @@ -18042,7 +18072,7 @@ snapshots: postcss-reduce-initial@7.0.9(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 caniuse-api: 3.0.0 postcss: 8.5.25 @@ -18602,14 +18632,14 @@ snapshots: sass-embedded: 1.100.0 webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - sass-loader@16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + sass-loader@16.0.8(@rspack/core@1.6.8)(sass-embedded@1.100.0)(sass@1.101.0)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: neo-async: 2.6.2 optionalDependencies: '@rspack/core': 1.6.8 sass: 1.101.0 sass-embedded: 1.100.0 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) sass@1.100.0: dependencies: @@ -18885,11 +18915,11 @@ snapshots: source-map-js: 1.2.1 webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - source-map-loader@5.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + source-map-loader@5.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) source-map-support@0.5.19: dependencies: @@ -19025,17 +19055,17 @@ snapshots: dependencies: webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - style-loader@3.3.4(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + style-loader@3.3.4(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) - style-loader@4.0.0(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + style-loader@4.0.0(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) stylehacks@7.0.11(postcss@8.5.25): dependencies: - browserslist: 4.28.5 + browserslist: 4.28.8 postcss: 8.5.25 postcss-selector-parser: 7.1.4 @@ -19188,7 +19218,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 + terser: 5.50.0 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: clean-css: 5.3.3 @@ -19203,7 +19233,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 + terser: 5.50.0 webpack: 5.105.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: clean-css: 5.3.3 @@ -19213,13 +19243,13 @@ snapshots: html-minifier-terser: 6.1.0 postcss: 8.5.25 - terser-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + terser-webpack-plugin@5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + terser: 5.50.0 + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: clean-css: 5.3.3 cssnano: 7.1.9(postcss@8.5.25) @@ -19231,14 +19261,14 @@ snapshots: terser@5.43.1: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 + acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 - terser@5.49.0: + terser@5.50.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 + acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -19310,15 +19340,15 @@ snapshots: dependencies: tslib: 2.8.1 - ts-loader@9.6.1(loader-utils@2.0.4)(typescript@5.9.3)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + ts-loader@9.6.1(loader-utils@2.0.4)(typescript@5.9.3)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: chalk: 4.1.2 - enhanced-resolve: 5.24.3 + enhanced-resolve: 5.24.5 micromatch: 4.0.8 semver: 7.6.3 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: loader-utils: 2.0.4 @@ -19343,7 +19373,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.24.5 tapable: 2.3.3 tsconfig-paths: 4.2.0 @@ -19400,8 +19430,6 @@ snapshots: ua-parser-js@0.7.41: {} - undici-types@7.24.6: {} - undici-types@8.3.0: {} undici@8.9.0: {} @@ -19442,6 +19470,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -19532,7 +19566,7 @@ snapshots: webidl-conversions@3.0.1: optional: true - webpack-dev-middleware@6.1.3(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + webpack-dev-middleware@6.1.3(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -19540,7 +19574,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) webpack-dev-middleware@7.4.2(tslib@2.8.1)(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: @@ -19555,7 +19589,7 @@ snapshots: transitivePeerDependencies: - tslib - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: colorette: 2.0.20 memfs: 4.57.8(tslib@2.8.1) @@ -19564,7 +19598,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) transitivePeerDependencies: - tslib optional: true @@ -19637,19 +19671,19 @@ snapshots: webpack-sources@3.5.1: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: typed-assert: 1.0.9 webpack: 5.105.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: - html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)))(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)): dependencies: typed-assert: 1.0.9 - webpack: 5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) + webpack: 5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25) optionalDependencies: - html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + html-webpack-plugin: 5.6.7(@rspack/core@1.6.8)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) webpack-virtual-modules@0.6.2: {} @@ -19661,12 +19695,12 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.17.0 - acorn-import-phases: 1.0.4(acorn@8.17.0) - browserslist: 4.28.5 + acorn: 8.18.0 + acorn-import-phases: 1.0.4(acorn@8.18.0) + browserslist: 4.28.8 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.3 - es-module-lexer: 2.1.0 + enhanced-resolve: 5.24.5 + es-module-lexer: 2.3.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -19702,12 +19736,12 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.17.0 - acorn-import-phases: 1.0.4(acorn@8.17.0) - browserslist: 4.28.5 + acorn: 8.18.0 + acorn-import-phases: 1.0.4(acorn@8.18.0) + browserslist: 4.28.8 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.3 - es-module-lexer: 2.1.0 + enhanced-resolve: 5.24.5 + es-module-lexer: 2.3.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -19735,23 +19769,23 @@ snapshots: - postcss - uglify-js - webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25): + webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.17.0 - browserslist: 4.28.5 + acorn: 8.18.0 + browserslist: 4.28.8 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.3 - es-module-lexer: 2.1.0 + enhanced-resolve: 5.24.5 + es-module-lexer: 2.3.1 eslint-scope: 5.1.1 events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.0(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) + minimizer-webpack-plugin: 5.6.1(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)(webpack@5.109.2(clean-css@5.3.3)(cssnano@7.1.9(postcss@8.5.25))(csso@5.0.5)(esbuild@0.25.9)(html-minifier-terser@6.1.0)(postcss@8.5.25)) neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 From 1612b4817f8bff2c3f02a3d03e79cb1031acbbd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:57:23 +0200 Subject: [PATCH 06/31] build(deps): bump the github-actions group across 2 directories with 8 updates (#12175) Bumps the github-actions group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.6` | `4.37.7` | | [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.37.6` | `4.37.7` | | [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.6` | `4.37.7` | | [Alfresco/alfresco-build-tools/.github/actions/send-teams-notification](https://github.com/alfresco/alfresco-build-tools) | `18.21.3` | `18.23.0` | | [Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml](https://github.com/alfresco/alfresco-build-tools) | `18.21.3` | `18.23.0` | | [Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment](https://github.com/alfresco/alfresco-build-tools) | `18.21.3` | `18.23.0` | | [github/gh-aw-actions/setup](https://github.com/github/gh-aw-actions) | `0.86.1` | `0.86.2` | Bumps the github-actions group with 1 update in the /.github/actions/setup directory: [Alfresco/alfresco-build-tools/.github/actions/git-latest-tag](https://github.com/alfresco/alfresco-build-tools). Updates `github/codeql-action/init` from 4.37.6 to 4.37.7 - [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/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/autobuild` from 4.37.6 to 4.37.7 - [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/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7 - [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/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `Alfresco/alfresco-build-tools/.github/actions/send-teams-notification` from 18.21.3 to 18.23.0 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b...98bcfbe06aafffdc0e9a790f352602316f82303b) Updates `Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml` from 18.21.3 to 18.23.0 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b...98bcfbe06aafffdc0e9a790f352602316f82303b) Updates `Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment` from 18.21.3 to 18.23.0 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b...98bcfbe06aafffdc0e9a790f352602316f82303b) Updates `github/gh-aw-actions/setup` from 0.86.1 to 0.86.2 - [Release notes](https://github.com/github/gh-aw-actions/releases) - [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/gh-aw-actions/compare/8914f47b6c1bb8a802c7549f5ac1a81434b66403...6aab9e5b5c91c615506061f09bedd81a23babe3c) Updates `Alfresco/alfresco-build-tools/.github/actions/git-latest-tag` from 18.21.3 to 18.23.0 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b...98bcfbe06aafffdc0e9a790f352602316f82303b) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification dependency-version: 18.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml dependency-version: 18.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment dependency-version: 18.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/gh-aw-actions/setup dependency-version: 0.86.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag dependency-version: 18.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/setup/action.yml | 2 +- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/notify-on-an-bdu-label.yml | 2 +- .github/workflows/stale-pr-cleanup.yml | 2 +- .github/workflows/supply-chain-pr-instructions.yml | 2 +- .github/workflows/supply-chain-review.lock.yml | 14 +++++++------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 69a6feef8b..436e1fa0aa 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -35,7 +35,7 @@ runs: - name: get latest tag sha if: ${{ inputs.full-setup == 'true' }} id: tag-sha - uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b # v18.21.3 + uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 - name: load "NPM TAG" if: ${{ inputs.full-setup == 'true' }} id: set-npm-tag diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 88c32e79f5..9a2c2a18d4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v3.29.5 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 # 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@5595ccaf912efad79be6eef63a5619ff05969be3 # v3.29.5 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 # ℹ️ 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@5595ccaf912efad79be6eef63a5619ff05969be3 # v3.29.5 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 diff --git a/.github/workflows/notify-on-an-bdu-label.yml b/.github/workflows/notify-on-an-bdu-label.yml index 2248248c4e..639dbfe315 100644 --- a/.github/workflows/notify-on-an-bdu-label.yml +++ b/.github/workflows/notify-on-an-bdu-label.yml @@ -53,7 +53,7 @@ jobs: - name: Send Teams notification if: steps.check_label_timing.outputs.should_notify == 'true' - uses: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification@5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b # v18.21.3 + uses: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 with: webhook-url: ${{ secrets.TEAMS_NOTIFICATION_ADF_BDU_WEBHOOK }} skip_checkout: true diff --git a/.github/workflows/stale-pr-cleanup.yml b/.github/workflows/stale-pr-cleanup.yml index c50211b485..e14f9155c7 100644 --- a/.github/workflows/stale-pr-cleanup.yml +++ b/.github/workflows/stale-pr-cleanup.yml @@ -11,4 +11,4 @@ permissions: jobs: stale-pr-cleanup: - uses: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml@5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b # v18.21.3 + uses: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 diff --git a/.github/workflows/supply-chain-pr-instructions.yml b/.github/workflows/supply-chain-pr-instructions.yml index 9ebc9bee49..5c5166f57f 100644 --- a/.github/workflows/supply-chain-pr-instructions.yml +++ b/.github/workflows/supply-chain-pr-instructions.yml @@ -16,7 +16,7 @@ jobs: permissions: pull-requests: write steps: - - uses: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment@5177eca5d3d71342d7f7e0a2a4d74cc16b1eeb1b # v18.21.3 + - uses: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 with: comment-identifier: supply-chain-review-instructions comment-body: | diff --git a/.github/workflows/supply-chain-review.lock.yml b/.github/workflows/supply-chain-review.lock.yml index 8b1a7db227..8a114a76b5 100644 --- a/.github/workflows/supply-chain-review.lock.yml +++ b/.github/workflows/supply-chain-review.lock.yml @@ -41,7 +41,7 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 +# - github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 @@ -125,7 +125,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -494,7 +494,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1128,7 +1128,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1404,7 +1404,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1653,7 +1653,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1732,7 +1732,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@8914f47b6c1bb8a802c7549f5ac1a81434b66403 # v0.86.1 + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} From f5d3e3df3735cf90e829ae36f5422d2a69ed06e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:19:25 +0100 Subject: [PATCH 07/31] build(deps): bump actions/github-script from 7.1.0 to 9.0.0 (#12176) Bumps [actions/github-script](https://github.com/actions/github-script) from 7.1.0 to 9.0.0. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7.1.0...3a2844b7e9c422d3c10d287c895573f7108da1b3) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/notify-on-an-bdu-label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notify-on-an-bdu-label.yml b/.github/workflows/notify-on-an-bdu-label.yml index 639dbfe315..cafc0dff62 100644 --- a/.github/workflows/notify-on-an-bdu-label.yml +++ b/.github/workflows/notify-on-an-bdu-label.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Check if label was added after PR creation (with time threshold) id: check_label_timing - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const prCreatedAt = new Date('${{ github.event.pull_request.created_at }}'); From 6eb32c3ec0233022d62aaa9cd3419b89117d382d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:20:27 +0100 Subject: [PATCH 08/31] build(deps-dev): bump jasmine-core from 5.13.0 to 6.3.0 (#12118) Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 5.13.0 to 6.3.0. - [Release notes](https://github.com/jasmine/jasmine/releases) - [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md) - [Commits](https://github.com/jasmine/jasmine/compare/v5.13.0...v6.3.0) --- updated-dependencies: - dependency-name: jasmine-core dependency-version: 6.3.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5ed0791e5e..790f5677b8 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "eslint-plugin-unicorn": "61.0.2", "graphql": "16.14.0", "husky": "9.1.7", - "jasmine-core": "5.13.0", + "jasmine-core": "6.3.0", "jasmine-reporters": "2.5.2", "jsonc-eslint-parser": "2.4.2", "karma": "6.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 761e4f0a1b..7018bdebea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,8 +252,8 @@ importers: specifier: 9.1.7 version: 9.1.7 jasmine-core: - specifier: 5.13.0 - version: 5.13.0 + specifier: 6.3.0 + version: 6.3.0 jasmine-reporters: specifier: 2.5.2 version: 2.5.2 @@ -274,7 +274,7 @@ importers: version: 5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) karma-jasmine-html-reporter: specifier: 2.2.0 - version: 2.2.0(jasmine-core@5.13.0)(karma-jasmine@5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)))(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) + version: 2.2.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)))(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) lint-staged: specifier: 17.2.0 version: 17.2.0 @@ -6679,8 +6679,8 @@ packages: jasmine-core@4.6.1: resolution: {integrity: sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==} - jasmine-core@5.13.0: - resolution: {integrity: sha512-vsYjfh7lyqvZX5QgqKc4YH8phs7g96Z8bsdIFNEU3VqXhlHaq+vov/Fgn/sr6MiUczdZkyXRC3TX369Ll4Nzbw==} + jasmine-core@6.3.0: + resolution: {integrity: sha512-eMm5qBovNjNoGOcgE/W207+wrcK5zrQv0Rg/rWGboUJUmZp0dFCpHTyjpuDAfCwRCqg7f9U2q2jtv/aUuzdCQg==} jasmine-reporters@2.5.2: resolution: {integrity: sha512-qdewRUuFOSiWhiyWZX8Yx3YNQ9JG51ntBEO4ekLQRpktxFTwUHy24a86zD/Oi2BRTKksEdfWQZcQFqzjqIkPig==} @@ -16790,7 +16790,7 @@ snapshots: jasmine-core@4.6.1: {} - jasmine-core@5.13.0: {} + jasmine-core@6.3.0: {} jasmine-reporters@2.5.2: dependencies: @@ -16887,9 +16887,9 @@ snapshots: transitivePeerDependencies: - supports-color - karma-jasmine-html-reporter@2.2.0(jasmine-core@5.13.0)(karma-jasmine@5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)))(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)): + karma-jasmine-html-reporter@2.2.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)))(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)): dependencies: - jasmine-core: 5.13.0 + jasmine-core: 6.3.0 karma: 6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) karma-jasmine: 5.1.0(karma@6.4.4(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)) From d7b1d74b052c137215e3605500596c512ab1b01a Mon Sep 17 00:00:00 2001 From: Bartosz Sekula Date: Tue, 18 Aug 2026 10:15:58 +0200 Subject: [PATCH 09/31] AAE-50130 js-yaml quadratic CPU consumption (#12178) --- pnpm-lock.yaml | 14 +++++++------- pnpm-workspace.yaml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7018bdebea..32c680d625 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,12 +6,12 @@ settings: overrides: fast-uri: 3.1.5 - js-yaml@4.2.0: 4.3.0 + js-yaml@4.2.0: 4.3.1 + brace-expansion@5.0.8: 5.0.9 brace-expansion@<1.1.18: 1.1.18 svgo: 4.0.2 shell-quote: 1.9.0 adm-zip: 0.6.0 - brace-expansion@5.0.8: 5.0.9 minimatch@10.2.5: 10.2.6 axios: 1.18.0 serialize-javascript: '>=7.0.5' @@ -6716,8 +6716,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true jsdoc-type-pratt-parser@7.3.0: @@ -15142,7 +15142,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -15152,7 +15152,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -16831,7 +16831,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 965dd4910c..cbc68f342d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,7 +8,7 @@ minimumReleaseAgeStrict: true overrides: # Security fixes fast-uri: 3.1.5 - "js-yaml@4.2.0": "4.3.0" + "js-yaml@4.2.0": "4.3.1" "brace-expansion@5.0.8": "5.0.9" "brace-expansion@<1.1.18": "1.1.18" svgo: 4.0.2 From dfbe1adbd5f8137ea411bc3abd60c179950228e2 Mon Sep 17 00:00:00 2001 From: Alex Molodyh <140214274+amolodyh-hyland@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:56:10 -0700 Subject: [PATCH 10/31] AAE-50098 Fix unrendered rich text expressions in form submissions (#12169) --- .../widgets/core/form-field.model.spec.ts | 52 +++++ .../widgets/core/form-field.model.ts | 21 ++ .../components/form-cloud.component.spec.ts | 70 ++++++- .../form/components/form-cloud.component.ts | 31 ++- .../display-rich-text.widget.spec.ts | 45 +++++ .../display-rich-text.widget.ts | 23 +-- .../rich-text-expression-resolver.spec.ts | 137 +++++++++++++ .../rich-text-expression-resolver.ts | 111 +++++++++++ .../src/lib/form/public-api.ts | 1 + .../form-cloud-submission-values.spec.ts | 185 ++++++++++++++++++ .../services/form-cloud-submission-values.ts | 80 ++++++++ .../start-process-cloud.component.spec.ts | 59 +++++- .../start-process-cloud.component.ts | 38 +++- 13 files changed, 830 insertions(+), 23 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.ts create mode 100644 lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.ts diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index 0e4c65e9b4..6649655f66 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -36,6 +36,58 @@ describe('FormFieldModel', () => { expect(model.json).toBe(json); }); + it('should return an isolated authored value snapshot', () => { + const authoredValue = { blocks: [{ data: { text: '${field.name}' } }] }; + const model = new FormFieldModel(new FormModel(), { id: 'richText', type: FormFieldTypes.DISPLAY_RICH_TEXT, value: authoredValue }); + + const snapshot = model.authoredValue as typeof authoredValue; + snapshot.blocks[0].data.text = 'changed'; + + expect((model.authoredValue as typeof authoredValue).blocks[0].data.text).toBe('${field.name}'); + expect(authoredValue.blocks[0].data.text).toBe('${field.name}'); + }); + + it('should not capture authored values for other field types', () => { + const model = new FormFieldModel(new FormModel(), { id: 'json', type: FormFieldTypes.JSON, value: { content: 'value' } }); + + expect(model.authoredValue).toBeUndefined(); + }); + + it('should return undefined for authored values that cannot be cloned', () => { + const circularValue: { self?: unknown } = {}; + circularValue.self = circularValue; + + const circularValueModel = new FormFieldModel(new FormModel(), { + id: 'circular', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: circularValue + }); + const bigintValueModel = new FormFieldModel(new FormModel(), { + id: 'bigint', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: BigInt(1) + }); + + expect(circularValueModel.authoredValue).toBeUndefined(); + expect(bigintValueModel.authoredValue).toBeUndefined(); + }); + + it('should preserve authored value when form data overrides the field value', () => { + const authoredValue = { blocks: [{ data: { text: '${field.name}' } }] }; + const savedValue = { blocks: [{ data: { text: 'John' } }] }; + const form = new FormModel( + { + fields: [{ id: 'richText', name: 'richText', type: FormFieldTypes.DISPLAY_RICH_TEXT, value: authoredValue }] + }, + { richText: savedValue } + ); + const model = form.getFieldById('richText'); + + expect(model.value).toEqual(savedValue); + expect(model.authoredValue).toEqual(authoredValue); + expect(model.authoredValue).not.toBe(authoredValue); + }); + it('should setup with json config', () => { const json = { fieldType: '', diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index 6241cf8f84..d38d692ab6 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -38,12 +38,28 @@ export type FieldOptionType = 'rest' | 'manual' | 'variable'; export type FieldSelectionType = 'single' | 'multiple'; export type FieldAlignmentType = 'vertical' | 'horizontal'; +const isJsonPrimitive = (value: unknown): value is null | string | number | boolean => + value === null || ['string', 'number', 'boolean'].includes(typeof value); + +const cloneJsonCompatibleValue = (value: unknown): unknown => { + if (value === undefined || isJsonPrimitive(value)) { + return value; + } + + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return undefined; + } +}; + // Maps to FormFieldRepresentation export class FormFieldModel extends FormWidgetModel { private _value: string; private _readOnly: boolean = false; private _isValid: boolean = true; private _required: boolean = false; + private readonly _authoredValue: unknown; readonly defaultDateFormat: string = 'D-M-YYYY'; readonly defaultDateTimeFormat: string = 'D-M-YYYY hh:mm A'; @@ -123,6 +139,10 @@ export class FormFieldModel extends FormWidgetModel { } } + get authoredValue(): unknown { + return cloneJsonCompatibleValue(this._authoredValue); + } + get readOnly(): boolean { if (this.form?.readOnly) { return true; @@ -183,6 +203,7 @@ export class FormFieldModel extends FormWidgetModel { constructor(form: any, json?: any, parent?: RepeatableSectionModel) { super(form, json); + this._authoredValue = json?.type === FormFieldTypes.DISPLAY_RICH_TEXT ? cloneJsonCompatibleValue(json.value) : undefined; if (json) { this.fieldType = json.fieldType; this.id = this.getId(json.id, parent); diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts index ba87073a06..5ef60cf0b2 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts @@ -17,6 +17,7 @@ import { VersionCompatibilityService, AlfrescoApiService } from '@alfresco/adf-content-services'; import { + ADF_DISPLAY_TEXT_SETTINGS, ContentLinkModel, CoreModule, FormFieldModel, @@ -117,7 +118,8 @@ describe('FormCloudComponent', () => { useValue: {} }, { provide: FormRenderingService, useClass: CloudFormRenderingService }, - { provide: FORM_CLOUD_FIELD_VALIDATORS_TOKEN, useValue: [fakeValidator] } + { provide: FORM_CLOUD_FIELD_VALIDATORS_TOKEN, useValue: [fakeValidator] }, + { provide: ADF_DISPLAY_TEXT_SETTINGS, useValue: { enableExpressionEvaluation: true } } ] }); const apiService = TestBed.inject(AlfrescoApiService); @@ -874,6 +876,39 @@ describe('FormCloudComponent', () => { expect(savedForm).toEqual(formModel); }); + it('should materialize unrendered rich text expressions when saving a task form', () => { + spyOn(formCloudService, 'saveTaskForm').and.returnValue(of(undefined)); + const formModel = new FormModel({ + id: '23', + taskId: '123-223', + fields: [ + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: 'Hello ${field.name}' } }] } + }, + { id: 'name', type: FormFieldTypes.TEXT, value: 'John' } + ] + }); + const originalValues = JSON.parse(JSON.stringify(formModel.values)); + formComponent.form = formModel; + formComponent.taskId = formModel.taskId; + formComponent.appName = 'test-app'; + + formComponent.saveTaskForm(); + + expect(formCloudService.saveTaskForm).toHaveBeenCalledWith( + 'test-app', + formModel.taskId, + undefined, + formModel.id, + jasmine.objectContaining({ + richText: { blocks: [{ type: 'paragraph', data: { text: 'Hello John' } }] } + }) + ); + expect(formModel.values).toEqual(originalValues); + }); + it('should handle error during form save', () => { const error = 'Error'; spyOn(formCloudService, 'saveTaskForm').and.callFake(() => throwError(error)); @@ -981,6 +1016,39 @@ describe('FormCloudComponent', () => { expect(completedForm).toBe(formComponent.form); }); + it('should materialize unrendered rich text expressions when completing a task form', () => { + spyOn(formCloudService, 'completeTaskForm').and.returnValue(of(undefined)); + const formModel = new FormModel({ + id: '23', + taskId: '123-223', + fields: [ + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: '${field.name}' } }] } + }, + { id: 'name', type: FormFieldTypes.TEXT, value: 'John' } + ] + }); + formComponent.form = formModel; + formComponent.taskId = formModel.taskId; + formComponent.appName = 'test-app'; + + formComponent.completeTaskForm('complete'); + + expect(formCloudService.completeTaskForm).toHaveBeenCalledWith( + 'test-app', + formModel.taskId, + undefined, + formModel.id, + jasmine.objectContaining({ + richText: { blocks: [{ type: 'paragraph', data: { text: 'John' } }] } + }), + 'complete', + undefined + ); + }); + it('should open confirmation dialog on complete task', async () => { formComponent.form = new FormModel({ confirmMessage: { diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts index 33f50d4fb8..fd2a681e99 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts @@ -35,9 +35,12 @@ import { filter, map, switchMap } from 'rxjs/operators'; import { ConfirmDialogComponent, ContentLinkModel, + ADF_DISPLAY_TEXT_SETTINGS, + DisplayTextWidgetSettings, FormatSpacePipe, FormBaseComponent, FormEvent, + FormExpressionService, FormFieldModel, FormRulesEvent, FormFieldValidator, @@ -67,6 +70,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { A11yModule } from '@angular/cdk/a11y'; +import { getExpressionEvaluationEnabled$, materializeSubmissionValues } from '../services/form-cloud-submission-values'; interface FormFieldRuntimeState { value: any; @@ -228,6 +232,8 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, protected changeDetector = inject(ChangeDetectorRef); private readonly destroyRef = inject(DestroyRef); + private readonly expressions = inject(FormExpressionService); + private enableExpressionEvaluation = false; private get currentForm(): FormModel | undefined { return super.form; @@ -252,6 +258,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, constructor() { const injectedFieldValidators = inject(FORM_CLOUD_FIELD_VALIDATORS_TOKEN, { optional: true }); const tabNavEnabledToken = inject(ADF_FORM_TAB_NAV_ENABLED, { optional: true }); + const displayTextSettings = inject | DisplayTextWidgetSettings>(ADF_DISPLAY_TEXT_SETTINGS, { + optional: true + }); super(); this.loadInjectedFieldValidators(injectedFieldValidators); @@ -270,6 +279,12 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, } } + getExpressionEvaluationEnabled$(displayTextSettings) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enabled) => { + this.enableExpressionEvaluation = enabled; + }); + this.formService.formContentClicked.pipe(takeUntilDestroyed()).subscribe((content) => { if (content instanceof UploadWidgetContentLinkModel) { this.form.setNodeIdValueForViewersLinkedToUploadWidget(content); @@ -482,7 +497,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, saveTaskForm() { if (this.form && this.appName && this.taskId) { this.formCloudService - .saveTaskForm(this.appName, this.taskId, this.processInstanceId, `${this.form.id}`, this.form.values) + .saveTaskForm(this.appName, this.taskId, this.processInstanceId, `${this.form.id}`, this.getSubmissionValues()) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { @@ -523,7 +538,15 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, private completeForm(outcome?: string, outcomeId?: string) { if (this.form && this.appName && this.taskId) { this.formCloudService - .completeTaskForm(this.appName, this.taskId, this.processInstanceId, `${this.form.id}`, this.form.values, outcome, this.appVersion) + .completeTaskForm( + this.appName, + this.taskId, + this.processInstanceId, + `${this.form.id}`, + this.getSubmissionValues(), + outcome, + this.appVersion + ) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { @@ -536,6 +559,10 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, } } + private getSubmissionValues(): FormValues { + return materializeSubmissionValues(this.form, { enableExpressionEvaluation: this.enableExpressionEvaluation }, this.expressions); + } + parseForm(formCloudRepresentationJSON?: any): FormModel | null { if (formCloudRepresentationJSON) { const formValues: FormValues = {}; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts index 80062e72f7..cc646237b5 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts @@ -200,6 +200,51 @@ describe('DisplayRichTextWidgetComponent', () => { expect(widget.field.value.blocks[0].data.text).toBe('Hello John'); }); + it('should resolve from authored value after saved data rehydrates the field', () => { + const form = new FormModel( + { + fields: [ + { + id: 'richText1', + name: 'richText1', + type: 'display-rich-text', + value: { + blocks: [{ type: 'paragraph', data: { text: 'Hello ${field.name}' } }] + } + }, + { id: 'name', name: 'name', type: 'text', value: 'John' } + ] + }, + { + richText1: { + blocks: [{ type: 'paragraph', data: { text: 'Hello John' } }] + }, + name: 'Jane' + } + ); + + widget.field = form.getFieldById('richText1'); + fixture.detectChanges(); + + expect(widget.field.value.blocks[0].data.text).toBe('Hello Jane'); + }); + + it('should preserve the current value when the authored value is unavailable', () => { + const form = new FormModel({ + fields: [{ id: 'richText1', type: 'display-rich-text' }] + }); + const currentValue = { + blocks: [{ type: 'paragraph', data: { text: 'Current value' } }] + }; + const field = form.getFieldById('richText1'); + field.value = currentValue; + widget.field = field; + + fixture.detectChanges(); + + expect(widget.field.value).toBe(currentValue); + }); + it('should resolve expressions in multiple blocks', () => { const form = new FormModel({ fields: [ diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts index 92708cf69c..785059ac9f 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts @@ -22,6 +22,7 @@ import { BaseDisplayTextWidgetComponent } from '@alfresco/adf-core'; import { DomSanitizer } from '@angular/platform-browser'; import { Subscription } from 'rxjs'; import { RichTextParserService } from '../../../services/rich-text-parser.service'; +import { resolveRichTextExpressions } from './rich-text-expression-resolver'; export const RICH_TEXT_PARSER_TOKEN = new InjectionToken('RichTextParserService', { factory: () => new RichTextParserService() @@ -66,7 +67,10 @@ export class DisplayRichTextWidgetComponent extends BaseDisplayTextWidgetCompone protected storeOriginalValue(): void { if (this.field) { - this.originalFieldValue = JSON.stringify(this.field.value); + const authoredValue = this.field.authoredValue; + if (authoredValue !== undefined) { + this.originalFieldValue = JSON.stringify(authoredValue); + } } } @@ -75,8 +79,10 @@ export class DisplayRichTextWidgetComponent extends BaseDisplayTextWidgetCompone return; } - const value = JSON.parse(JSON.stringify(this.field.value)); - this.applyExpressionsToBlocks(value); + const authoredValue = this.field.authoredValue; + if (authoredValue !== undefined) { + this.applyExpressionsToBlocks(authoredValue); + } } protected reevaluateExpressions(): void { @@ -89,16 +95,7 @@ export class DisplayRichTextWidgetComponent extends BaseDisplayTextWidgetCompone } private applyExpressionsToBlocks(value: any): void { - for (const block of value.blocks) { - if (block.type === 'list') { - for (const item of block.data.items) { - item.content = this.resolveExpressions(item.content, true); - } - } else { - block.data.text = this.resolveExpressions(block.data.text, true); - } - } - this.field.value = value; + this.field.value = resolveRichTextExpressions(value, (content) => this.resolveExpressions(content, true)); } private parseAndSanitize(): void { diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.spec.ts new file mode 100644 index 0000000000..07c3b3c5da --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.spec.ts @@ -0,0 +1,137 @@ +/*! + * @license + * Copyright © 2005-2026 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 { resolveRichTextExpressions } from './rich-text-expression-resolver'; + +describe('resolveRichTextExpressions', () => { + const resolve = (value: string) => value.replaceAll('${field.name}', 'John').replaceAll('${variable.status}', 'Active'); + + it('should resolve supported rich text content without mutating the input', () => { + const value = { + time: 1, + blocks: [ + { + type: 'paragraph', + data: { + text: 'Hello ${field.name}', + caption: 'Status: ${variable.status}', + content: [ + ['Cell ${field.name}'], + { + label: '${variable.status}' + } + ] + } + }, + { + type: 'list', + data: { + items: [ + { + content: '${field.name}', + items: [{ content: '${variable.status}' }] + } + ] + } + } + ], + version: '2.30.0' + }; + const originalValue = JSON.parse(JSON.stringify(value)); + + const result = resolveRichTextExpressions(value, resolve); + + expect(result).toEqual({ + time: 1, + blocks: [ + { + type: 'paragraph', + data: { + text: 'Hello John', + caption: 'Status: Active', + content: [['Cell John'], { label: 'Active' }] + } + }, + { + type: 'list', + data: { + items: [{ content: 'John', items: [{ content: 'Active' }] }] + } + } + ], + version: '2.30.0' + }); + expect(value).toEqual(originalValue); + expect(result).not.toBe(value); + }); + + it('should resolve a caller-owned clone without cloning it again', () => { + const value = { + blocks: [{ type: 'paragraph', data: { text: 'Hello ${field.name}' } }] + }; + + const result = resolveRichTextExpressions(value, resolve, { cloneValue: false }) as typeof value; + + expect(result).toBe(value); + expect(result.blocks[0].data.text).toBe('Hello John'); + }); + + it('should preserve unknown blocks and properties', () => { + const value = { + blocks: [ + { + type: 'custom', + data: { + label: '${field.name}' + }, + metadata: '${variable.status}' + } + ] + }; + + expect(resolveRichTextExpressions(value, resolve)).toEqual(value); + }); + + it('should not introduce missing content properties', () => { + const result = resolveRichTextExpressions({ blocks: [{ type: 'paragraph', data: {} }] }, resolve) as { + blocks: Array<{ data: Record }>; + }; + + expect(result.blocks[0].data).toEqual({}); + }); + + it('should return malformed values unchanged', () => { + const malformedValues = [null, undefined, 'text', [], {}, { blocks: null }]; + + malformedValues.forEach((value) => { + expect(resolveRichTextExpressions(value, resolve)).toBe(value); + }); + }); + + it('should return non-cloneable values without mutating them', () => { + const value: { + blocks: Array<{ data: { text: string } }>; + self?: unknown; + } = { + blocks: [{ data: { text: '${field.name}' } }] + }; + value.self = value; + + expect(resolveRichTextExpressions(value, resolve)).toBe(value); + expect(value.blocks[0].data.text).toBe('${field.name}'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.ts new file mode 100644 index 0000000000..9606b31f76 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/rich-text-expression-resolver.ts @@ -0,0 +1,111 @@ +/*! + * @license + * Copyright © 2005-2026 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. + */ + +type JsonObject = Record; + +export type RichTextExpressionResolver = (value: string) => string; + +export interface RichTextExpressionResolverOptions { + cloneValue?: boolean; +} + +const isJsonObject = (value: unknown): value is JsonObject => typeof value === 'object' && value !== null && !Array.isArray(value); + +const cloneJsonValue = (value: unknown): unknown => { + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return undefined; + } +}; + +const resolveNestedContent = (value: unknown, resolve: RichTextExpressionResolver): unknown => { + if (typeof value === 'string') { + return resolve(value); + } + + if (Array.isArray(value)) { + return value.map((entry) => resolveNestedContent(entry, resolve)); + } + + if (isJsonObject(value)) { + return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, resolveNestedContent(entry, resolve)])); + } + + return value; +}; + +const resolveListItems = (items: unknown, resolve: RichTextExpressionResolver): unknown => { + if (!Array.isArray(items)) { + return items; + } + + return items.map((item) => { + if (!isJsonObject(item)) { + return item; + } + + if (Object.hasOwn(item, 'content')) { + item.content = resolveNestedContent(item.content, resolve); + } + + if (Object.hasOwn(item, 'items')) { + item.items = resolveListItems(item.items, resolve); + } + + return item; + }); +}; + +export const resolveRichTextExpressions = ( + value: unknown, + resolve: RichTextExpressionResolver, + options: RichTextExpressionResolverOptions = {} +): unknown => { + if (!isJsonObject(value) || !Array.isArray(value.blocks)) { + return value; + } + + const resolvedValue = options.cloneValue === false ? value : cloneJsonValue(value); + if (!isJsonObject(resolvedValue) || !Array.isArray(resolvedValue.blocks)) { + return value; + } + + resolvedValue.blocks.forEach((block) => { + if (!isJsonObject(block) || !isJsonObject(block.data)) { + return; + } + + if (typeof block.data.text === 'string') { + block.data.text = resolve(block.data.text); + } + + if (typeof block.data.caption === 'string') { + block.data.caption = resolve(block.data.caption); + } + + if (Object.hasOwn(block.data, 'content')) { + block.data.content = resolveNestedContent(block.data.content, resolve); + } + + if (block.type === 'list' && Object.hasOwn(block.data, 'items')) { + block.data.items = resolveListItems(block.data.items, resolve); + } + }); + + return resolvedValue; +}; diff --git a/lib/process-services-cloud/src/lib/form/public-api.ts b/lib/process-services-cloud/src/lib/form/public-api.ts index 758c688f72..10256da84f 100644 --- a/lib/process-services-cloud/src/lib/form/public-api.ts +++ b/lib/process-services-cloud/src/lib/form/public-api.ts @@ -43,5 +43,6 @@ export * from './services/form-cloud.service'; export * from './services/content-cloud-node-selector.service'; export * from './services/process-cloud-content.service'; export * from './services/display-mode.service'; +export * from './services/form-cloud-submission-values'; export * from './form-cloud.module'; diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.spec.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.spec.ts new file mode 100644 index 0000000000..c5df7f6b1e --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.spec.ts @@ -0,0 +1,185 @@ +/*! + * @license + * Copyright © 2005-2026 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 { TestBed } from '@angular/core/testing'; +import { FormExpressionService, FormFieldModel, FormFieldTypes, FormModel } from '@alfresco/adf-core'; +import { firstValueFrom, of } from 'rxjs'; +import { getExpressionEvaluationEnabled$, materializeSubmissionValues } from './form-cloud-submission-values'; + +describe('getExpressionEvaluationEnabled$', () => { + it('should return the configured static value', async () => { + const enabled = await firstValueFrom(getExpressionEvaluationEnabled$({ enableExpressionEvaluation: true })); + + expect(enabled).toBe(true); + }); + + it('should return values emitted by observable settings', async () => { + const enabled = await firstValueFrom(getExpressionEvaluationEnabled$(of({ enableExpressionEvaluation: true }))); + + expect(enabled).toBe(true); + }); + + it('should return false when settings are unavailable', async () => { + const enabled = await firstValueFrom(getExpressionEvaluationEnabled$(undefined)); + + expect(enabled).toBe(false); + }); +}); + +describe('materializeSubmissionValues', () => { + let expressions: FormExpressionService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [FormExpressionService] + }); + expressions = TestBed.inject(FormExpressionService); + }); + + it('should resolve root rich text values from the authored template without mutating the form', () => { + const authoredValue = { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Hello ${field.name} - ${variable.status} - ${field.missing} - ${field.unsafe}' + } + } + ] + }; + const form = new FormModel({ + fields: [ + { id: 'richText', name: 'richText', type: FormFieldTypes.DISPLAY_RICH_TEXT, value: authoredValue }, + { id: 'name', name: 'name', type: FormFieldTypes.TEXT, value: 'John' }, + { id: 'unsafe', name: 'unsafe', type: FormFieldTypes.TEXT, value: 'John' } + ], + variables: [{ id: 'status', name: 'status', type: 'string', value: 'Active' }] + }); + const richTextField = form.getFieldById('richText'); + richTextField.value = { blocks: [{ type: 'paragraph', data: { text: 'stale rendered value' } }] }; + const originalValues = JSON.parse(JSON.stringify(form.values)); + const originalDefinition = JSON.parse(JSON.stringify(form.json)); + + const values = materializeSubmissionValues(form, { enableExpressionEvaluation: true }, expressions); + + expect(values.richText).toEqual({ + blocks: [ + { + type: 'paragraph', + data: { + text: 'Hello John - Active - - <b>John</b>' + } + } + ] + }); + expect(form.values).toEqual(originalValues); + expect(form.json).toEqual(originalDefinition); + expect(richTextField.value).toEqual({ blocks: [{ type: 'paragraph', data: { text: 'stale rendered value' } }] }); + }); + + it('should produce stable values across repeated materialization', () => { + const form = new FormModel({ + fields: [ + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: '${field.name}' } }] } + }, + { id: 'name', type: FormFieldTypes.TEXT, value: 'John' } + ] + }); + + const firstValues = materializeSubmissionValues(form, { enableExpressionEvaluation: true }, expressions); + const secondValues = materializeSubmissionValues(form, { enableExpressionEvaluation: true }, expressions); + + expect(secondValues).toEqual(firstValues); + }); + + it('should return a shallow clone without resolving expressions when evaluation is disabled', () => { + const form = new FormModel({ + fields: [ + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: '${field.name}' } }] } + }, + { id: 'name', type: FormFieldTypes.TEXT, value: 'John' } + ] + }); + + const values = materializeSubmissionValues(form, { enableExpressionEvaluation: false }, expressions); + + expect(values).toEqual(form.values); + expect(values).not.toBe(form.values); + expect(values.richText).toBe(form.values.richText); + }); + + it('should isolate materialized repeatable section rows', () => { + const form = new FormModel(); + form.values = { + section: [ + { richText: 'saved row one', untouched: 'one' }, + { richText: 'saved row two', untouched: 'two' } + ], + name: 'John' + }; + const nameField = new FormFieldModel(form, { id: 'name', type: FormFieldTypes.TEXT, value: 'John' }); + const firstField = new FormFieldModel( + form, + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: 'First ${field.name}' } }] } + }, + { id: 'section', uid: 'richText-Row1', fields: {}, rowIndex: 0 } + ); + const secondField = new FormFieldModel( + form, + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: 'Second ${field.name}' } }] } + }, + { id: 'section', uid: 'richText-Row2', fields: {}, rowIndex: 1 } + ); + form.fieldsCache = [nameField, firstField, secondField]; + form.values.section = [ + { richText: 'saved row one', untouched: 'one' }, + { richText: 'saved row two', untouched: 'two' } + ]; + const originalSection = form.values.section; + const originalFirstRow = form.values.section[0]; + const originalSecondRow = form.values.section[1]; + + const values = materializeSubmissionValues(form, { enableExpressionEvaluation: true }, expressions); + + expect(values.section).toEqual([ + { + richText: { blocks: [{ type: 'paragraph', data: { text: 'First John' } }] }, + untouched: 'one' + }, + { + richText: { blocks: [{ type: 'paragraph', data: { text: 'Second John' } }] }, + untouched: 'two' + } + ]); + expect(values.section).not.toBe(originalSection); + expect(values.section[0]).not.toBe(originalFirstRow); + expect(values.section[1]).not.toBe(originalSecondRow); + expect(form.values.section).toBe(originalSection); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.ts new file mode 100644 index 0000000000..a43709e4e2 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-cloud-submission-values.ts @@ -0,0 +1,80 @@ +/*! + * @license + * Copyright © 2005-2026 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 { DisplayTextWidgetSettings, FormExpressionService, FormFieldTypes, FormModel, FormValues, ROW_ID_PREFIX } from '@alfresco/adf-core'; +import { isObservable, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { resolveRichTextExpressions } from '../components/widgets/display-rich-text/rich-text-expression-resolver'; + +type SubmissionRow = Record; + +const isSubmissionRow = (value: unknown): value is SubmissionRow => typeof value === 'object' && value !== null && !Array.isArray(value); + +export interface FormCloudSubmissionValuesOptions { + enableExpressionEvaluation: boolean; +} + +export const getExpressionEvaluationEnabled$ = ( + settings: Observable | DisplayTextWidgetSettings | null | undefined +): Observable => + isObservable(settings) + ? settings.pipe(map((value) => value?.enableExpressionEvaluation ?? false)) + : of(settings?.enableExpressionEvaluation ?? false); + +export const materializeSubmissionValues = ( + form: FormModel, + options: FormCloudSubmissionValuesOptions, + expressions: FormExpressionService +): FormValues => { + const values = { ...form.values }; + + if (!options.enableExpressionEvaluation) { + return values; + } + + for (const field of form.getFormFields([FormFieldTypes.DISPLAY_RICH_TEXT])) { + const { authoredValue, parent } = field; + if (authoredValue === undefined || parent?.isTemplate) { + continue; + } + + const materializedValue = resolveRichTextExpressions(authoredValue, (content) => expressions.resolveExpressions(form, content, true), { + cloneValue: false + }); + + if (!parent) { + values[field.id] = materializedValue; + continue; + } + + const sectionValues = values[parent.id]; + const sectionRow = Array.isArray(sectionValues) ? sectionValues[parent.rowIndex] : undefined; + if (!isSubmissionRow(sectionRow)) { + continue; + } + + const materializedRows = [...sectionValues]; + const fieldId = field.id.split(ROW_ID_PREFIX)[0]; + materializedRows[parent.rowIndex] = { + ...sectionRow, + [fieldId]: materializedValue + }; + values[parent.id] = materializedRows; + } + + return values; +}; diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts index 71136e5c5b..0f9559bc03 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts @@ -17,8 +17,8 @@ import { DebugElement, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-core'; -import { of, throwError } from 'rxjs'; +import { ADF_DISPLAY_TEXT_SETTINGS, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-core'; +import { Subject, of, throwError } from 'rxjs'; import { StartProcessCloudService } from '../services/start-process-cloud.service'; import { FormCloudService } from '../../../form/services/form-cloud.service'; import { FormCloudComponent } from '../../../form/components/form-cloud.component'; @@ -47,7 +47,7 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing'; import { MatButtonHarness } from '@angular/material/button/testing'; -import { FormCloudDisplayMode } from '../../../services/form-fields.interfaces'; +import { FormCloudDisplayMode, FormContent } from '../../../services/form-fields.interfaces'; import { MatDialogHarness } from '@angular/material/dialog/testing'; import { MatDialog } from '@angular/material/dialog'; import { ReactiveFormsModule } from '@angular/forms'; @@ -92,7 +92,10 @@ describe('StartProcessCloudComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [StartProcessCloudComponent, ReactiveFormsModule, StartProcessScreenCloudComponent], - providers: [provideScreen(screenId, MockedTaskScreenCloudComponent)] + providers: [ + provideScreen(screenId, MockedTaskScreenCloudComponent), + { provide: ADF_DISPLAY_TEXT_SETTINGS, useValue: { enableExpressionEvaluation: true } } + ] }); processService = TestBed.inject(StartProcessCloudService); formCloudService = TestBed.inject(FormCloudService); @@ -417,6 +420,54 @@ describe('StartProcessCloudComponent', () => { expect(startBtn.disabled).toBe(false); }); + it('should keep the start action unavailable while the form definition is loading', async () => { + const formDefinition = new Subject(); + formDefinitionSpy.and.returnValue(formDefinition); + typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form'); + await selectOptionByName('processwithform'); + + const startButton = fixture.nativeElement.querySelector('#button-start'); + expect(startButton).toBeNull(); + expect(startProcessWithFormSpy).not.toHaveBeenCalled(); + }); + + it('should materialize unrendered rich text expressions when starting a process', async () => { + formDefinitionSpy.and.returnValue(of(fakeStartForm)); + component.processDefinitionCurrent = fakeProcessDefinitions[2]; + component.processPayloadCloud.processDefinitionKey = fakeProcessDefinitions[2].key; + component.processInstanceName.setValue('My process'); + fixture.detectChanges(); + await fixture.whenStable(); + + const form = new FormModel({ + fields: [ + { + id: 'richText', + type: FormFieldTypes.DISPLAY_RICH_TEXT, + value: { blocks: [{ type: 'paragraph', data: { text: 'Hello ${field.name}' } }] } + }, + { id: 'name', type: FormFieldTypes.TEXT, value: 'John' } + ] + }); + const formElement = fixture.debugElement.query(By.css('adf-cloud-form')); + const startButton = fixture.debugElement.query(By.css('#button-start')); + + formElement.triggerEventHandler('formLoaded', form); + fixture.detectChanges(); + startButton.triggerEventHandler('click', null); + + expect(startProcessWithFormSpy).toHaveBeenCalledWith( + component.appName, + fakeProcessDefinitions[2].formKey, + fakeProcessDefinitions[2].version, + jasmine.objectContaining({ + values: jasmine.objectContaining({ + richText: { blocks: [{ type: 'paragraph', data: { text: 'Hello John' } }] } + }) + }) + ); + }); + it('should be able to start a process with form full display mode', async () => { component.displayModeConfigurations = [ { diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts index c06299d703..40126c5722 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts @@ -30,10 +30,14 @@ import { ViewEncapsulation } from '@angular/core'; import { + ADF_DISPLAY_TEXT_SETTINGS, ConfirmDialogComponent, ContentLinkModel, + DisplayTextWidgetSettings, + FormExpressionService, FormModel, FormOutcomeEvent, + FormValues, IconModule, InplaceFormInputComponent, LocalizedDatePipe, @@ -65,6 +69,7 @@ import { FormCustomOutcomesComponent } from '../../../form/components/form-cloud import { MatDialog } from '@angular/material/dialog'; import { StartProcessScreenCloudComponent } from '../../../screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component'; import { TaskTypeResolverService } from '../../../services/task-type-resolver/task-type-resolver.service'; +import { getExpressionEvaluationEnabled$, materializeSubmissionValues } from '../../../form/services/form-cloud-submission-values'; const MAX_NAME_LENGTH: number = 255; const PROCESS_DEFINITION_DEBOUNCE: number = 300; @@ -211,6 +216,11 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { private readonly hasVisibleOutcomesSubject = new BehaviorSubject(false); private readonly dialog = inject(MatDialog); private readonly taskTypeResolverService = inject(TaskTypeResolverService); + private readonly expressions = inject(FormExpressionService); + private readonly displayTextSettings = inject | DisplayTextWidgetSettings>(ADF_DISPLAY_TEXT_SETTINGS, { + optional: true + }); + private enableExpressionEvaluation = false; private screenSubmitPayload: unknown; @@ -218,8 +228,12 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { showCompleteButton = false; get isProcessFormValid(): boolean { - if (this.hasForm && this.isFormCloudLoaded) { - return (this.formCloud ? !Object.keys(this.formCloud.values).length : false) || this.formCloud?.isValid || this.isProcessStarting; + if (this.hasForm) { + if (!this.isFormCloudLoaded || !this.formCloud) { + return false; + } + + return !Object.keys(this.formCloud.values).length || this.formCloud.isValid || this.isProcessStarting; } else if (this.hasScreen) { return true; } else { @@ -268,6 +282,12 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { constructor() { this.startProcessButtonLabel = this.defaultStartProcessButtonLabel; this.cancelButtonLabel = this.defaultCancelProcessButtonLabel; + + getExpressionEvaluationEnabled$(this.displayTextSettings) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enabled) => { + this.enableExpressionEvaluation = enabled; + }); } ngOnInit() { @@ -482,6 +502,14 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { } startProcessWithoutConfirmation() { + let submissionValues = this.screenSubmitPayload; + if (this.hasForm) { + if (!this.formCloud) { + return; + } + submissionValues = this.getFormSubmissionValues(this.formCloud); + } + this.isProcessStarting = true; let action: Observable; @@ -495,7 +523,7 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { processName: this.processInstanceName.value, processDefinitionKey: this.processPayloadCloud.processDefinitionKey, variables: this.variables ?? {}, - values: this.hasForm ? this.formCloud.values : this.screenSubmitPayload, + values: submissionValues, outcome: this.customOutcomeName }) ); @@ -524,6 +552,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { }); } + private getFormSubmissionValues(form: FormModel): FormValues { + return materializeSubmissionValues(form, { enableExpressionEvaluation: this.enableExpressionEvaluation }, this.expressions); + } + startProcess() { if (!this.formCloud?.confirmMessage?.show) { this.startProcessWithoutConfirmation(); From f907f8ef10f1af51e6dbd6b493c7c3e653abe9ea Mon Sep 17 00:00:00 2001 From: Alex Molodyh <140214274+amolodyh-hyland@users.noreply.github.com> Date: Tue, 18 Aug 2026 19:37:03 -0700 Subject: [PATCH 11/31] AAE-50099 Fix parameterized validation message interpolation (#12170) --- docs/core/models/form-field.model.md | 3 +- .../widgets/amount/amount.widget.spec.ts | 73 ++++++++++++++- .../widgets/amount/amount.widget.ts | 16 +--- .../widgets/core/error-message.model.ts | 20 ++++- .../widgets/core/form-field.model.spec.ts | 88 ++++++++++++++++++- .../widgets/core/form-field.model.ts | 27 +++++- .../widgets/decimal/decimal.component.spec.ts | 68 ++++++++++++++ .../widgets/decimal/decimal.component.ts | 18 ++-- .../multiline-text.widget.spec.ts | 53 +++++++++++ .../multiline-text/multiline-text.widget.ts | 14 +-- .../widgets/number/number.widget.spec.ts | 66 ++++++++++++++ .../widgets/number/number.widget.ts | 18 ++-- .../widgets/text/text.widget.spec.ts | 52 +++++++++++ .../components/widgets/text/text.widget.ts | 16 ++-- 14 files changed, 467 insertions(+), 65 deletions(-) diff --git a/docs/core/models/form-field.model.md b/docs/core/models/form-field.model.md index ed98e18f8d..ab88e0ab5a 100644 --- a/docs/core/models/form-field.model.md +++ b/docs/core/models/form-field.model.md @@ -52,7 +52,8 @@ Contains the value and metadata for a field of a [`Form`](../../../lib/process-s | columns | [`ContainerColumnModel`](../../../lib/core/src/lib/form/components/widgets/core/container-column.model.ts)\[] | \[] | Column definitions for a container field | | rows | [`ContainerRowModel`](../../../lib/core/src/lib/form/components/widgets/core/container-row.model.ts)\[] | \[] | Row definitions for a repeatable section field | | emptyOption | [`FormFieldOption`](../../../lib/core/src/lib/form/components/widgets/core/form-field-option.ts) | | Dropdown menu item to use when no option is chosen | -| validationSummary | string | | Error/information message added during field validation (see [`FormFieldValidator`](../../../lib/core/src/lib/form/components/widgets/core/form-field-validator.ts) interface) | +| validationSummary | [`ErrorMessageModel`](../../../lib/core/src/lib/form/components/widgets/core/error-message.model.ts) | | Error/information message added during field validation (see [`FormFieldValidator`](../../../lib/core/src/lib/form/components/widgets/core/form-field-validator.ts) interface) | +| validationSummaryChanges$ | Observable<[`ErrorMessageModel`](../../../lib/core/src/lib/form/components/widgets/core/error-message.model.ts)> | | Replays the current validation summary to subscribers and emits the completed summary after each validation | ## Details diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts index 8168f609a4..ac4ab4a1d4 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts @@ -23,13 +23,14 @@ import { FormModel } from '../core/form.model'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; import { FormService } from '../../../services/form.service'; import { FormFieldEvent } from '../../../events/form-field.event'; import { TranslationService } from '../../../../translation/translation.service'; import { registerLocaleData } from '@angular/common'; import localeDe from '@angular/common/locales/de'; import localeDeExtra from '@angular/common/locales/extra/de'; +import { TranslateService } from '@ngx-translate/core'; registerLocaleData(localeDe, 'de-DE', localeDeExtra); @@ -394,6 +395,76 @@ describe('AmountWidgetComponent - rendering', () => { expect(errors[0].trim()).toContain('FORM.FIELD.VALIDATOR.INVALID_NUMBER'); }); + describe('when validation runs without amount interaction', () => { + const validatorTranslations = { + FORM: { + FIELD: { + VALIDATOR: { + NOT_LESS_THAN: "Can't be less than {{ minValue }}", + NOT_GREATER_THAN: "Can't be greater than {{ maxValue }}" + } + } + } + }; + let amountField: FormFieldModel; + let form: FormModel; + + beforeEach(async () => { + const translateService = TestBed.inject(TranslateService); + translateService.setTranslation('en', validatorTranslations); + await firstValueFrom(translateService.use('en')); + + form = new FormModel({ taskId: '' }, undefined, false, formService); + amountField = new FormFieldModel(form, { + id: 'amount-id', + type: FormFieldTypes.AMOUNT, + value: 1, + minValue: '10' + }); + form.fieldsCache = [amountField]; + amountField.validate(); + fixture.componentRef.setInput('field', amountField); + fixture.detectChanges(); + }); + + it('should render updated parameters after direct revalidation', async () => { + const formField = await testingUtils.formField.get(); + let errors = await formField.getTextErrors(); + expect(errors[0]).toContain("Can't be less than 10"); + + amountField.value = 10; + amountField.minValue = '1'; + amountField.maxValue = '5'; + amountField.validate(); + fixture.detectChanges(); + + errors = await formField.getTextErrors(); + expect(errors[0]).toContain("Can't be greater than 5"); + expect(errors[0]).not.toContain('{{'); + }); + + it('should render updated parameters after sibling field revalidation', async () => { + const siblingField = new FormFieldModel(form, { + id: 'sibling-id', + type: FormFieldTypes.TEXT, + value: 'before' + }); + form.fieldsCache = [amountField, siblingField]; + amountField.value = 10; + amountField.minValue = '1'; + amountField.maxValue = '5'; + + siblingField.value = 'after'; + form.onFormFieldChanged(siblingField); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + expect(errors[0]).toContain("Can't be greater than 5"); + expect(errors[0]).not.toContain('{{'); + }); + }); + describe('when form model has left labels', () => { it('should have left labels classes on leftLabels true', async () => { widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), { diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts index 23ad1b867b..be96468ff7 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts @@ -25,6 +25,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; +import { getValidationSummaryTranslationParameters } from '../core/error-message.model'; import { WidgetComponent } from '../widget.component'; import { filter, isObservable, Observable } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -121,7 +122,9 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { this.subscribeToFieldChanges(); this.setInitialValues(); this.initErrorStateMatcher(); - this.updateTranslateParameters(); + this.field.validationSummaryChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((validationSummary) => { + this.translateParameters = getValidationSummaryTranslationParameters(validationSummary); + }); } } @@ -143,7 +146,6 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { } } this.markAsTouched(); - this.updateTranslateParameters(); } amountWidgetOnFocus(): void { @@ -163,7 +165,6 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { this.field.value = this.amountWidgetValue; super.onFieldChanged(this.field); this.markAsTouched(); - this.updateTranslateParameters(); } setInitialValues(): void { @@ -188,7 +189,6 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { } else if (!this.isInputInFocus) { this.amountWidgetValue = ev.field.value; } - this.updateTranslateParameters(); }); } @@ -208,12 +208,4 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { !!this.field.validationSummary?.message || (this.isInvalidFieldRequired() && this.isTouched()) }; } - - private updateTranslateParameters(): void { - if (this.field?.validationSummary?.isActive()) { - this.translateParameters = this.field.validationSummary.getAttributesAsJsonObj(); - } else { - this.translateParameters = {}; - } - } } diff --git a/lib/core/src/lib/form/components/widgets/core/error-message.model.ts b/lib/core/src/lib/form/components/widgets/core/error-message.model.ts index 19a7b4f183..c55236257c 100644 --- a/lib/core/src/lib/form/components/widgets/core/error-message.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/error-message.model.ts @@ -17,24 +17,36 @@ export class ErrorMessageModel { message: string = ''; - attributes: Map = null; + attributes: Map = new Map(); constructor(obj?: any) { this.message = obj?.message || ''; - this.attributes = obj?.attributes || new Map(); + + if (obj?.attributes) { + this.attributes = obj.attributes; + } } isActive(): boolean { return !!this.message; } - getAttributesAsJsonObj() { - const result = {}; + getAttributesAsJsonObj(): Record { + const result: Record = {}; if (this.attributes.size > 0) { this.attributes.forEach((value, key) => { result[key] = typeof value === 'string' ? value : JSON.stringify(value); }); } + return result; } } + +export const getValidationSummaryTranslationParameters = (validationSummary?: ErrorMessageModel): Record => { + if (validationSummary?.isActive()) { + return validationSummary.getAttributesAsJsonObj(); + } + + return {}; +}; diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index 6649655f66..03bac3ae4b 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -17,9 +17,10 @@ import { DateFnsUtils } from '../../../../common'; import { FormRulesEvent } from '../../../events/form-rules.event'; -import { firstValueFrom, map, Subject, take, timeout } from 'rxjs'; +import { firstValueFrom, map, skip, Subject, take, timeout } from 'rxjs'; +import { ErrorMessageModel, getValidationSummaryTranslationParameters } from './error-message.model'; import { FormFieldTypes } from './form-field-types'; -import { RequiredFieldValidator } from './form-field-validator'; +import { MinValueFieldValidator, RequiredFieldValidator } from './form-field-validator'; import { FormFieldModel } from './form-field.model'; import { FormModel } from './form.model'; @@ -1274,6 +1275,57 @@ describe('FormFieldModel', () => { }); }); + describe('validation summary changes', () => { + const createField = (): FormFieldModel => { + const form = new FormModel(); + form.fieldValidators = [new MinValueFieldValidator()]; + + return new FormFieldModel(form, { + id: 'number-field', + type: FormFieldTypes.NUMBER, + value: 1, + minValue: '10' + }); + }; + + it('should replay an inactive validation summary before the first validation', async () => { + const field = new FormFieldModel(new FormModel()); + + const validationSummary = await firstValueFrom(field.validationSummaryChanges$); + + expect(validationSummary).toEqual(jasmine.any(ErrorMessageModel)); + expect(validationSummary.isActive()).toBe(false); + }); + + it('should replay the completed validation summary when subscribing after validation', async () => { + const field = createField(); + field.validate(); + + const validationSummary = await firstValueFrom(field.validationSummaryChanges$); + + expect(validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN'); + expect(validationSummary.attributes.get('minValue')).toBe('10'); + }); + + it('should emit completed summaries when validation state changes', async () => { + const field = createField(); + const invalidSummaryPromise = firstValueFrom(field.validationSummaryChanges$.pipe(skip(1))); + + field.validate(); + const invalidSummary = await invalidSummaryPromise; + + field.value = 10; + const validSummaryPromise = firstValueFrom(field.validationSummaryChanges$.pipe(skip(1))); + field.validate(); + const validSummary = await validSummaryPromise; + + expect(invalidSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN'); + expect(invalidSummary.attributes.get('minValue')).toBe('10'); + expect(validSummary.isActive()).toBe(false); + expect(validSummary.attributes.size).toBe(0); + }); + }); + it('should fail validation for readOnly required display-external-property field with null value', () => { const form = new FormModel(); const field = new FormFieldModel(form, { @@ -2108,3 +2160,35 @@ describe('FormFieldTypes', () => { }); }); }); + +describe('ErrorMessageModel', () => { + it('should initialize empty attributes when attributes are omitted', () => { + const errorMessage = new ErrorMessageModel(); + + expect(errorMessage.attributes).toEqual(new Map()); + }); + + it('should retain provided attributes', () => { + const attributes = new Map([['minValue', '10']]); + + const errorMessage = new ErrorMessageModel({ attributes }); + + expect(errorMessage.attributes).toBe(attributes); + }); +}); + +describe('getValidationSummaryTranslationParameters', () => { + it('should return validation attributes when the summary is active', () => { + const validationSummary = new ErrorMessageModel({ + message: 'FORM.FIELD.VALIDATOR.NOT_LESS_THAN', + attributes: new Map([['minValue', '10']]) + }); + + expect(getValidationSummaryTranslationParameters(validationSummary)).toEqual({ minValue: '10' }); + }); + + it('should return empty parameters when the summary is inactive or omitted', () => { + expect(getValidationSummaryTranslationParameters(new ErrorMessageModel())).toEqual({}); + expect(getValidationSummaryTranslationParameters()).toEqual({}); + }); +}); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index d38d692ab6..884e23eb41 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -29,6 +29,7 @@ import { VariableConfig } from './form-field-variable-options'; import { DataColumn } from '../../../../datatable/data/data-column.model'; import { DateFnsUtils } from '../../../../common'; import { isValid as isValidDate } from 'date-fns'; +import { Observable, ReplaySubject } from 'rxjs'; import { ContainerRowModel } from './container-row.model'; import { RepeatableSectionModel, ROW_ID_PREFIX, TEMPLATE_ROW_ID } from './repeatable-section.model'; import { formFieldRuleHandler } from './handlers/form-field-rule.handler'; @@ -38,6 +39,13 @@ export type FieldOptionType = 'rest' | 'manual' | 'variable'; export type FieldSelectionType = 'single' | 'multiple'; export type FieldAlignmentType = 'vertical' | 'horizontal'; +interface ValidationSummaryChangesState { + subject: ReplaySubject; + observable: Observable; +} + +const validationSummaryChangesByField = new WeakMap(); + const isJsonPrimitive = (value: unknown): value is null | string | number | boolean => value === null || ['string', 'number', 'boolean'].includes(typeof value); @@ -126,7 +134,21 @@ export class FormFieldModel extends FormWidgetModel { // util members emptyOption: FormFieldOption; - validationSummary: ErrorMessageModel; + validationSummary: ErrorMessageModel = new ErrorMessageModel(); + + get validationSummaryChanges$(): Observable { + const existingState = validationSummaryChangesByField.get(this); + if (existingState) { + return existingState.observable; + } + + const subject = new ReplaySubject(1); + const observable = subject.asObservable(); + validationSummaryChangesByField.set(this, { subject, observable }); + subject.next(this.validationSummary); + + return observable; + } get value(): any { return this._value; @@ -193,11 +215,13 @@ export class FormFieldModel extends FormWidgetModel { for (const validator of validators) { if (!validator.validate(this)) { this._isValid = false; + validationSummaryChangesByField.get(this)?.subject.next(this.validationSummary); return this._isValid; } } this._isValid = true; + validationSummaryChangesByField.get(this)?.subject.next(this.validationSummary); return this._isValid; } @@ -242,7 +266,6 @@ export class FormFieldModel extends FormWidgetModel { this.enableFractions = json.enableFractions; this.currency = json.currency; this.dateDisplayFormat = json.dateDisplayFormat || this.getDefaultDateFormat(json); - this.validationSummary = new ErrorMessageModel(); this.tooltip = json.tooltip || ''; this.selectionType = json.selectionType; this.alignmentType = json.alignmentType; diff --git a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.spec.ts b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.spec.ts index d4ab7fab85..a1fbbab018 100644 --- a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.spec.ts +++ b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.spec.ts @@ -22,8 +22,20 @@ import { UnitTestingUtils } from '../../../../testing'; import { FormService } from '../../../services/form.service'; import { FormFieldModel, FormFieldTypes, FormModel } from '../core'; import { DecimalWidgetComponent } from './decimal.component'; +import { TranslateService } from '@ngx-translate/core'; describe('DecimalComponent', () => { + const validatorTranslations = { + FORM: { + FIELD: { + VALIDATOR: { + NOT_LESS_THAN: "Can't be less than {{ minValue }}", + NOT_GREATER_THAN: "Can't be greater than {{ maxValue }}", + INVALID_DECIMAL_PRECISION: 'Precision {{ precision }}' + } + } + } + }; let loader: HarnessLoader; let widget: DecimalWidgetComponent; let fixture: ComponentFixture; @@ -107,6 +119,62 @@ describe('DecimalComponent', () => { }); }); + describe('when validation runs without widget interaction', () => { + let field: FormFieldModel; + + beforeEach(() => { + const translateService = TestBed.inject(TranslateService); + translateService.use('en').subscribe(); + translateService.setTranslation('en', validatorTranslations); + field = new FormFieldModel(new FormModel({ taskId: '' }), { + id: 'decimal-id', + type: FormFieldTypes.DECIMAL, + value: 1, + minValue: 10 + }); + field.validate(); + field.form.showAllValidationErrors = true; + fixture.componentRef.setInput('field', field); + fixture.detectChanges(); + }); + + it('should render the minimum value in the message when initial validation fails', async () => { + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain("Can't be less than 10"); + }); + + it('should render the updated maximum value when programmatic revalidation fails', async () => { + field.value = 10; + field.minValue = '1'; + field.maxValue = '5'; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain("Can't be greater than 5"); + }); + + it('should render decimal precision when programmatic revalidation fails', async () => { + field.value = 1.234; + field.minValue = '1'; + field.precision = 2; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Precision 2'); + }); + }); + describe('when form model has left labels', () => { it('should have left labels classes on leftLabels true', async () => { widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), { diff --git a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.ts b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.ts index a88390caf7..a25bbb1b36 100644 --- a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.ts +++ b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.ts @@ -16,13 +16,15 @@ */ import { NgIf } from '@angular/common'; -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { ErrorStateMatcher } from '@angular/material/core'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; +import { getValidationSummaryTranslationParameters } from '../core/error-message.model'; import { WidgetComponent } from '../widget.component'; @Component({ @@ -46,19 +48,21 @@ import { WidgetComponent } from '../widget.component'; export class DecimalWidgetComponent extends WidgetComponent implements OnInit { errorStateMatcher: ErrorStateMatcher; translateParameters: Record = {}; + private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { this.initErrorStateMatcher(); + this.field.validationSummaryChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((validationSummary) => { + this.translateParameters = getValidationSummaryTranslationParameters(validationSummary); + }); } onBlur(): void { this.markAsTouched(); - this.updateTranslateParameters(); } onDecimalFieldChanged(): void { this.onFieldChanged(this.field); - this.updateTranslateParameters(); } private initErrorStateMatcher(): void { @@ -67,12 +71,4 @@ export class DecimalWidgetComponent extends WidgetComponent implements OnInit { !this.field.isValid && this.isTouched() }; } - - private updateTranslateParameters(): void { - if (this.field.validationSummary?.isActive()) { - this.translateParameters = this.field.validationSummary.getAttributesAsJsonObj(); - } else { - this.translateParameters = {}; - } - } } diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts index b2398bf024..96ed448e98 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts @@ -25,8 +25,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { of, Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; describe('MultilineTextWidgetComponentComponent', () => { + const validatorTranslations = { + FORM: { + FIELD: { + VALIDATOR: { + AT_LEAST_LONG: 'Minimum {{ minLength }}', + NO_LONGER_THAN: 'Maximum {{ maxLength }}' + } + } + } + }; let loader: HarnessLoader; let widget: MultilineTextWidgetComponentComponent; let fixture: ComponentFixture; @@ -109,6 +120,48 @@ describe('MultilineTextWidgetComponentComponent', () => { }); }); + describe('when validation runs without widget interaction', () => { + let field: FormFieldModel; + + beforeEach(() => { + const translateService = TestBed.inject(TranslateService); + translateService.use('en').subscribe(); + translateService.setTranslation('en', validatorTranslations); + field = new FormFieldModel(new FormModel({ taskId: '' }), { + id: 'multiline-text-id', + type: FormFieldTypes.MULTILINE_TEXT, + value: 'text', + minLength: 10 + }); + field.validate(); + field.form.showAllValidationErrors = true; + fixture.componentRef.setInput('field', field); + fixture.detectChanges(); + }); + + it('should render the minimum length in the message when initial validation fails', async () => { + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Minimum 10'); + }); + + it('should render the updated maximum length when programmatic revalidation fails', async () => { + field.value = 'too long'; + field.minLength = 1; + field.maxLength = 5; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Maximum 5'); + }); + }); + describe('when is required', () => { beforeEach(() => { widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts index ecfad39ae3..53fd4c37aa 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts @@ -27,6 +27,7 @@ import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; import { isObservable } from 'rxjs'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; +import { getValidationSummaryTranslationParameters } from '../core/error-message.model'; import { WidgetComponent } from '../widget.component'; @Component({ @@ -56,6 +57,9 @@ export class MultilineTextWidgetComponentComponent extends WidgetComponent imple ngOnInit(): void { this.initErrorStateMatcher(); + this.field.validationSummaryChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((validationSummary) => { + this.translateParameters = getValidationSummaryTranslationParameters(validationSummary); + }); if (this.enableCustomMessage != null) { if (isObservable(this.enableCustomMessage)) { this.enableCustomMessage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { @@ -73,12 +77,10 @@ export class MultilineTextWidgetComponentComponent extends WidgetComponent imple onBlur(): void { this.markAsTouched(); - this.updateTranslateParameters(); } onMultilineTextFieldChanged(): void { this.onFieldChanged(this.field); - this.updateTranslateParameters(); } private initErrorStateMatcher(): void { @@ -87,12 +89,4 @@ export class MultilineTextWidgetComponentComponent extends WidgetComponent imple !this.field.isValid && this.isTouched() }; } - - private updateTranslateParameters(): void { - if (this.field.validationSummary?.isActive()) { - this.translateParameters = this.field.validationSummary.getAttributesAsJsonObj(); - } else { - this.translateParameters = {}; - } - } } diff --git a/lib/core/src/lib/form/components/widgets/number/number.widget.spec.ts b/lib/core/src/lib/form/components/widgets/number/number.widget.spec.ts index 3ef03a164e..e853c0c43b 100644 --- a/lib/core/src/lib/form/components/widgets/number/number.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/number/number.widget.spec.ts @@ -22,8 +22,20 @@ import { UnitTestingUtils } from '../../../../testing'; import { FormFieldModel, FormFieldTypes, FormModel } from '../core'; import { NumberWidgetComponent } from './number.widget'; import { DecimalNumberPipe } from '../../../../pipes'; +import { TranslateService } from '@ngx-translate/core'; describe('NumberWidgetComponent', () => { + const validatorTranslations = { + FORM: { + FIELD: { + VALIDATOR: { + NOT_LESS_THAN: "Can't be less than {{ minValue }}", + NOT_GREATER_THAN: "Can't be greater than {{ maxValue }}", + NO_LONGER_THAN: 'Maximum length {{ maxLength }}' + } + } + } + }; let loader: HarnessLoader; let widget: NumberWidgetComponent; let fixture: ComponentFixture; @@ -175,6 +187,60 @@ describe('NumberWidgetComponent', () => { }); }); + describe('when validation runs without widget interaction', () => { + let field: FormFieldModel; + + beforeEach(() => { + const translateService = TestBed.inject(TranslateService); + translateService.use('en').subscribe(); + translateService.setTranslation('en', validatorTranslations); + field = new FormFieldModel(new FormModel({ taskId: '' }), { + id: 'number-id', + type: FormFieldTypes.NUMBER, + value: 1, + minValue: 10 + }); + field.validate(); + fixture.componentRef.setInput('field', field); + fixture.detectChanges(); + }); + + it('should render the minimum value in the message when initial validation fails', async () => { + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain("Can't be less than 10"); + }); + + it('should render the updated maximum value when programmatic revalidation fails', async () => { + field.value = 10; + field.minValue = '1'; + field.maxValue = '5'; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain("Can't be greater than 5"); + }); + + it('should render the maximum length when programmatic revalidation fails', async () => { + field.value = 12345678901; + field.minValue = '1'; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Maximum length 10'); + }); + }); + describe('when form model has left labels', () => { it('should have left labels classes on leftLabels true', async () => { widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), { diff --git a/lib/core/src/lib/form/components/widgets/number/number.widget.ts b/lib/core/src/lib/form/components/widgets/number/number.widget.ts index 113219a0ef..e7f378ca1d 100644 --- a/lib/core/src/lib/form/components/widgets/number/number.widget.ts +++ b/lib/core/src/lib/form/components/widgets/number/number.widget.ts @@ -18,7 +18,8 @@ /* eslint-disable @angular-eslint/component-selector */ import { NgIf } from '@angular/common'; -import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { ErrorStateMatcher } from '@angular/material/core'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -26,6 +27,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; import { DecimalNumberPipe } from '../../../../pipes'; +import { getValidationSummaryTranslationParameters } from '../core/error-message.model'; import { WidgetComponent } from '../widget.component'; @Component({ @@ -53,6 +55,7 @@ export class NumberWidgetComponent extends WidgetComponent implements OnInit { translateParameters: Record = {}; private readonly decimalNumberPipe = inject(DecimalNumberPipe); + private readonly destroyRef = inject(DestroyRef); ngOnInit() { if (this.field.readOnly) { @@ -61,11 +64,13 @@ export class NumberWidgetComponent extends WidgetComponent implements OnInit { this.displayValue = this.field.value; } this.initErrorStateMatcher(); + this.field.validationSummaryChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((validationSummary) => { + this.translateParameters = getValidationSummaryTranslationParameters(validationSummary); + }); } onBlur(): void { this.markAsTouched(); - this.updateTranslateParameters(); } protected onNumberChange(value: string) { @@ -74,7 +79,6 @@ export class NumberWidgetComponent extends WidgetComponent implements OnInit { } this.onFieldChanged(this.field); - this.updateTranslateParameters(); } private initErrorStateMatcher(): void { @@ -83,12 +87,4 @@ export class NumberWidgetComponent extends WidgetComponent implements OnInit { !!this.field.validationSummary?.message || (this.isInvalidFieldRequired() && this.isTouched()) }; } - - private updateTranslateParameters(): void { - if (this.field.validationSummary?.isActive()) { - this.translateParameters = this.field.validationSummary.getAttributesAsJsonObj(); - } else { - this.translateParameters = {}; - } - } } diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts index 48e576800a..816ad455a7 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts @@ -27,9 +27,20 @@ import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; import { of, Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; describe('TextWidgetComponent', () => { const form = new FormModel({ taskId: 'fake-task-id' }); + const validatorTranslations = { + FORM: { + FIELD: { + VALIDATOR: { + AT_LEAST_LONG: 'Minimum {{ minLength }}', + NO_LONGER_THAN: 'Maximum {{ maxLength }}' + } + } + } + }; let loader: HarnessLoader; let widget: TextWidgetComponent; @@ -64,6 +75,47 @@ describe('TextWidgetComponent', () => { }); }); + describe('when validation runs without widget interaction', () => { + let field: FormFieldModel; + + beforeEach(() => { + const translateService = TestBed.inject(TranslateService); + translateService.use('en').subscribe(); + translateService.setTranslation('en', validatorTranslations); + field = new FormFieldModel(form, { + id: 'text-id', + type: FormFieldTypes.TEXT, + value: 'text', + minLength: 10 + }); + field.validate(); + fixture.componentRef.setInput('field', field); + fixture.detectChanges(); + }); + + it('should render the minimum length in the message when initial validation fails', async () => { + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Minimum 10'); + }); + + it('should render the updated maximum length when programmatic revalidation fails', async () => { + field.value = 'too long'; + field.minLength = 1; + field.maxLength = 5; + field.validate(); + fixture.detectChanges(); + + const formField = await testingUtils.formField.get(); + const errors = await formField.getTextErrors(); + + expect(errors.length).toBe(1); + expect(errors[0]).toContain('Maximum 5'); + }); + }); + describe('when template is ready', () => { describe('and no mask is configured on text element', () => { it('should raise ngModelChange event', async () => { diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.ts index 665b315fd7..161afcaaef 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.ts @@ -19,6 +19,7 @@ import { NgIf, NgTemplateOutlet } from '@angular/common'; import { Component, Directive, inject, InjectionToken, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; import { ErrorStateMatcher } from '@angular/material/core'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -26,7 +27,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; import { WidgetComponent } from '../widget.component'; -import { ErrorMessageModel } from '../core/error-message.model'; +import { ErrorMessageModel, getValidationSummaryTranslationParameters } from '../core/error-message.model'; import { FormattableTextWidgetComponent } from '../core/formattable-text.widget'; import { DEFAULT_TEXT_MAX_LENGTH } from '../core/form-field-validator'; import { InputMaskDirective } from './text-mask.component'; @@ -97,6 +98,9 @@ export class TextWidgetComponent extends FormattableTextWidgetComponent { this.isMaskReversed = this.field.params['inputMaskReversed'] ? this.field.params['inputMaskReversed'] : false; } this.initErrorStateMatcher(); + this.field.validationSummaryChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((validationSummary) => { + this.translateParameters = getValidationSummaryTranslationParameters(validationSummary); + }); } onPaste(event: ClipboardEvent): void { @@ -125,12 +129,10 @@ export class TextWidgetComponent extends FormattableTextWidgetComponent { onBlur(): void { this.markAsTouched(); - this.updateTranslateParameters(); } onTextFieldChanged(): void { this.onFieldChanged(this.field); - this.updateTranslateParameters(); } private initErrorStateMatcher(): void { @@ -143,14 +145,6 @@ export class TextWidgetComponent extends FormattableTextWidgetComponent { }; } - private updateTranslateParameters(): void { - if (this.field.validationSummary?.isActive()) { - this.translateParameters = this.field.validationSummary.getAttributesAsJsonObj(); - } else { - this.translateParameters = {}; - } - } - private getLengthAfterPaste(input: HTMLInputElement, pastedValue: string): number { const value = input.value ?? ''; const selectionStart = input.selectionStart ?? value.length; From 70db1e383f1a33df8969c1989d91cbaffbda1896 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:23:43 +0200 Subject: [PATCH 12/31] AAE-50593 Fix: memory leaks in datatable column-resize directives (#12180) * fix: memory leaks in datatable column-resize directives Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * test: add regression test for mouseup listener cleanup on repeated mousedown Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> --- .../resizable/resizable.directive.ts | 5 ++++- .../resizable/resize-handle.directive.spec.ts | 22 +++++++++++++++++++ .../resizable/resize-handle.directive.ts | 4 +++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts index 70cb4d7baa..06d168a006 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts @@ -131,7 +131,10 @@ export class ResizableDirective implements OnInit, OnDestroy { .pipe(filter(() => !!this.currentRect)); mouseDrag - .pipe(map(({ clientX }) => this.getNewBoundingRectangle(this.startingRect, clientX + this.coverPadding))) + .pipe( + map(({ clientX }) => this.getNewBoundingRectangle(this.startingRect, clientX + this.coverPadding)), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((rectangle: BoundingRectangle) => { if (this.resizing.observers.length > 0) { this.zone.run(() => { diff --git a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.spec.ts b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.spec.ts index 793b15453a..69ea673f29 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.spec.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.spec.ts @@ -131,6 +131,28 @@ describe('ResizeHandleDirective', () => { expect(renderer.listen).toHaveBeenCalledWith(element.nativeElement, 'mousemove', jasmine.any(Function)); expect(renderer.listen).toHaveBeenCalledWith('document', 'mouseup', jasmine.any(Function)); }); + + it('should unregister previous mouseup listener before registering a new one on repeated mousedown', () => { + const firstUnlistenMouseUp = jasmine.createSpy('firstUnlistenMouseUp'); + const secondUnlistenMouseUp = jasmine.createSpy('secondUnlistenMouseUp'); + let mouseUpCallCount = 0; + + renderer.listen.and.callFake((_target: any, eventName: string, _callback: (event: MouseEvent) => void) => { + if (eventName === 'mouseup') { + mouseUpCallCount++; + return mouseUpCallCount === 1 ? firstUnlistenMouseUp : secondUnlistenMouseUp; + } + return () => {}; + }); + + const mouseEvent = new MouseEvent('mousedown', { cancelable: true }); + + mousedownCallback(mouseEvent); + expect(firstUnlistenMouseUp).not.toHaveBeenCalled(); + + mousedownCallback(mouseEvent); + expect(firstUnlistenMouseUp).toHaveBeenCalled(); + }); }); describe('keyboard resizing', () => { diff --git a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts index 726b0a1531..7ed69ca8cb 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts @@ -86,6 +86,7 @@ export class ResizeHandleDirective implements OnInit, OnDestroy { }); } + this.unlistenMouseUp?.(); this.unlistenMouseUp = this.renderer.listen('document', 'mouseup', (mouseUpEvent: MouseEvent) => { this.onMouseup(mouseUpEvent); }); @@ -96,7 +97,8 @@ export class ResizeHandleDirective implements OnInit, OnDestroy { private onMouseup(event: MouseEvent): void { this.unlistenMouseMove?.(); this.unlistenMouseMove = undefined; - this.unlistenMouseUp(); + this.unlistenMouseUp?.(); + this.unlistenMouseUp = undefined; this.resizableContainer.mouseup.next(event); } From f915c813f0bc45958f5251398345735b4e43dd96 Mon Sep 17 00:00:00 2001 From: Darren Thornton <6361057+dthornton-hyl@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:17:52 -0500 Subject: [PATCH 13/31] AAE-49862 Fix Form Rule Does Not Update Hidden Status of Outcomes on First Run (#12168) --- .../form-validation-service.interface.ts | 2 ++ .../src/lib/form/services/form.service.ts | 6 ++++ .../widget-visibility.service.spec.ts | 25 ++++++++++++++ .../services/widget-visibility.service.ts | 8 ++++- .../components/form-cloud.component.spec.ts | 34 +++++++++++++++++++ .../form/components/form-cloud.component.ts | 14 ++++---- 6 files changed, 80 insertions(+), 9 deletions(-) diff --git a/lib/core/src/lib/form/services/form-validation-service.interface.ts b/lib/core/src/lib/form/services/form-validation-service.interface.ts index 08b78bd6b5..7a5c5d6e7d 100644 --- a/lib/core/src/lib/form/services/form-validation-service.interface.ts +++ b/lib/core/src/lib/form/services/form-validation-service.interface.ts @@ -16,6 +16,7 @@ */ import { Subject } from 'rxjs'; +import { FormEvent } from '../events/form.event'; import { FormFieldEvent } from '../events/form-field.event'; import { FormRulesEvent } from '../events/form-rules.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; @@ -26,4 +27,5 @@ export interface FormValidationService { validateForm: Subject; validateFormField: Subject; formRulesEvent?: Subject; + formVisibilityRefreshed?: Subject; } diff --git a/lib/core/src/lib/form/services/form.service.ts b/lib/core/src/lib/form/services/form.service.ts index be9091917a..131430c972 100644 --- a/lib/core/src/lib/form/services/form.service.ts +++ b/lib/core/src/lib/form/services/form.service.ts @@ -62,6 +62,12 @@ export class FormService implements FormValidationService { formRulesEvent = new Subject(); + /** + * Emitted after form field/outcome visibility has been re-evaluated via WidgetVisibilityService.refreshVisibility. + * Internal ADF form-rendering event — not part of the FormValidationService contract. + */ + formVisibilityRefreshed = new Subject(); + constructor() { const injectedFieldValidators = inject(FORM_SERVICE_FIELD_VALIDATORS_TOKEN, { optional: true }); diff --git a/lib/core/src/lib/form/services/widget-visibility.service.spec.ts b/lib/core/src/lib/form/services/widget-visibility.service.spec.ts index 796a01afb9..a75f9fa768 100644 --- a/lib/core/src/lib/form/services/widget-visibility.service.spec.ts +++ b/lib/core/src/lib/form/services/widget-visibility.service.spec.ts @@ -19,6 +19,7 @@ import { TestBed } from '@angular/core/testing'; import { ContainerModel, FormFieldModel, FormFieldTypes, FormModel, TabModel } from '../components/widgets/core'; import { WidgetVisibilityModel } from '../models/widget-visibility.model'; import { WidgetVisibilityService } from './widget-visibility.service'; +import { FormService } from './form.service'; import { fakeFormJson, formTest, @@ -50,6 +51,30 @@ describe('WidgetVisibilityService', () => { service = TestBed.inject(WidgetVisibilityService); }); + it('should emit formVisibilityRefreshed when visibility is refreshed', () => { + const formService = TestBed.inject(FormService); + let emittedForm: FormModel | undefined; + + formService.formVisibilityRefreshed.subscribe((event) => { + emittedForm = event.form; + }); + + service.refreshVisibility(stubFormWithFields); + + expect(emittedForm).toBe(stubFormWithFields); + }); + + it('should not emit formVisibilityRefreshed when form is null', () => { + const formService = TestBed.inject(FormService); + let emitCount = 0; + + formService.formVisibilityRefreshed.subscribe(() => emitCount++); + + service.refreshVisibility(null); + + expect(emitCount).toBe(0); + }); + describe('should be able to evaluate next condition operations', () => { it('using == and return true', () => { const resultsArray = evaluateConditions( diff --git a/lib/core/src/lib/form/services/widget-visibility.service.ts b/lib/core/src/lib/form/services/widget-visibility.service.ts index 3e717afa2f..a7d1ea039d 100644 --- a/lib/core/src/lib/form/services/widget-visibility.service.ts +++ b/lib/core/src/lib/form/services/widget-visibility.service.ts @@ -15,16 +15,20 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { FormFieldModel, FormModel, TabModel, ContainerModel, FormOutcomeModel } from '../components/widgets/core'; +import { FormEvent } from '../events/form.event'; import { TaskProcessVariableModel } from '../models/task-process-variable.model'; import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model'; import { format, isValid, parse } from 'date-fns'; +import { FormService } from './form.service'; @Injectable({ providedIn: 'root' }) export class WidgetVisibilityService { + private readonly formService = inject(FormService); + private processVarList: TaskProcessVariableModel[]; private form: FormModel; @@ -45,6 +49,8 @@ export class WidgetVisibilityService { } form.getFormFields().map((field) => this.refreshEntityVisibility(field)); + + this.formService.formVisibilityRefreshed.next(new FormEvent(form)); } } diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts index 5ef60cf0b2..221f4dedb5 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts @@ -32,6 +32,8 @@ import { provideTranslations, AuthModule, FormFieldEvent, + FormEvent, + FormRulesEvent, NoopTranslateModule, NoopAuthModule, FORM_FIELD_VALIDATORS @@ -1299,6 +1301,38 @@ describe('FormCloudComponent', () => { expect(formComponent.visibleOutcomes).toEqual([]); }); + it('should recompute visibleOutcomes when form visibility is refreshed', () => { + formComponent.showCompleteButton = true; + const formModel = new FormModel(cloudFormMock); + formComponent.form = formModel; + + expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0); + + formModel.outcomes.forEach((outcome) => { + outcome.isVisible = false; + }); + + TestBed.inject(FormService).formVisibilityRefreshed.next(new FormEvent(formModel)); + + expect(formComponent.visibleOutcomes).toEqual([]); + }); + + it('should recompute visibleOutcomes when fieldValueChanged rule event fires', () => { + formComponent.showCompleteButton = true; + const formModel = new FormModel(cloudFormMock); + formComponent.form = formModel; + + expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0); + + formModel.outcomes.forEach((outcome) => { + outcome.isVisible = false; + }); + + TestBed.inject(FormService).formRulesEvent.next(new FormRulesEvent('fieldValueChanged', new FormEvent(formModel))); + + expect(formComponent.visibleOutcomes).toEqual([]); + }); + it('should raise [executeOutcome] event for formService', async () => { spyOn(formComponent.executeOutcome, 'emit'); diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts index fd2a681e99..c2ab42da40 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts @@ -30,7 +30,7 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { forkJoin, isObservable, Observable, of, Subscription } from 'rxjs'; +import { forkJoin, isObservable, merge, Observable, of, Subscription } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; import { ConfirmDialogComponent, @@ -306,11 +306,11 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, } }); - this.formService.formRulesEvent - .pipe( - filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id), - takeUntilDestroyed() - ) + merge( + this.formService.formVisibilityRefreshed.pipe(filter((event) => event.form?.id === this.form?.id)), + this.formService.formRulesEvent.pipe(filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id)) + ) + .pipe(takeUntilDestroyed()) .subscribe(() => this.recomputeVisibleOutcomes()); } @@ -595,7 +595,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, checkVisibility(field: FormFieldModel) { if (field?.form) { this.visibilityService.refreshVisibility(field.form); - this.recomputeVisibleOutcomes(); } } @@ -613,7 +612,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, this.setCheckParentVisibilityForValidationOnFields(); this.visibilityService.refreshVisibility(this.form); this.form.validateForm(); - this.recomputeVisibleOutcomes(); this.onFormLoaded(this.form); this.formService.formRulesEvent.next(new FormRulesEvent('dataRefreshed', new FormEvent(this.form))); this.onFormDataRefreshed(this.form); From 7b8d616a9f3861f0265da4ccb92177cc0663fd3b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:44:15 +0200 Subject: [PATCH 14/31] AAE-50678 Fix: report test coverage to SonarCloud (#12181), * fix: enable LCOV coverage reporting for SonarCloud Add lcov reporter to all karma configs, upload coverage artifacts from unit test matrix jobs, and add a SonarCloud scan job that merges coverage reports and runs the sonar-scanner with proper LCOV paths. Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: pass secrets to unit-test-workflow and set SONAR_HOST_URL for SonarCloud Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * fix: add test outputs to nx.json so NX caches and restores coverage reports Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * test: add unit tests for Chart model to verify coverage reporting * fix: use find to locate lcov.info in downloaded artifacts for SonarCloud coverage Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * test: add fake file with unit test to verify coverage reporting Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * ci: add full SonarCloud scan workflow on develop push Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * test: remove fake coverage-canary file and its spec Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * fix: replace secrets inherit with explicit SONAR_TOKEN in workflow call Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> Co-authored-by: Eugenio Romano Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/pull-request.yml | 2 + .github/workflows/sonar-develop.yml | 22 +++ .github/workflows/unit-test-workflow.yml | 67 +++++++- lib/content-services/karma.conf.js | 2 +- lib/core/karma.conf.js | 2 +- lib/extensions/karma.conf.js | 2 +- lib/insights/karma.conf.js | 2 +- .../diagram/models/chart/chart.model.spec.ts | 148 ++++++++++++++++++ lib/process-services-cloud/karma.conf.js | 2 +- lib/process-services/karma.conf.js | 2 +- nx.json | 3 +- sonar-project.properties | 11 ++ 12 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/sonar-develop.yml create mode 100644 lib/insights/src/lib/diagram/models/chart/chart.model.spec.ts create mode 100644 sonar-project.properties diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 424b51941b..371fe5e0fa 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -254,6 +254,8 @@ jobs: name: "Unit Tests" needs: [setup] uses: ./.github/workflows/unit-test-workflow.yml + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: base_ref: ${{ github.base_ref || 'develop' }} diff --git a/.github/workflows/sonar-develop.yml b/.github/workflows/sonar-develop.yml new file mode 100644 index 0000000000..bb8d17828f --- /dev/null +++ b/.github/workflows/sonar-develop.yml @@ -0,0 +1,22 @@ +name: "SonarCloud Full Scan (develop)" + +on: + push: + branches: + - develop + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + full-unit-tests-and-sonar-scan: + name: "Full Unit Tests + SonarCloud Scan" + uses: ./.github/workflows/unit-test-workflow.yml + secrets: inherit + with: + full: true diff --git a/.github/workflows/unit-test-workflow.yml b/.github/workflows/unit-test-workflow.yml index 8759f8b863..2b52941aec 100644 --- a/.github/workflows/unit-test-workflow.yml +++ b/.github/workflows/unit-test-workflow.yml @@ -2,12 +2,21 @@ name: "Unit Tests Workflow" on: workflow_call: + secrets: + SONAR_TOKEN: + description: 'Token for SonarCloud analysis' + required: false inputs: base_ref: description: 'Base branch for affected calculation' required: false type: string default: 'develop' + full: + description: 'Run the full (non-affected) test suite for every project instead of only affected ones' + required: false + type: boolean + default: false jobs: generate-affected-matrix: @@ -30,9 +39,15 @@ jobs: id: set-matrix env: BASE_REF: ${{ inputs.base_ref }} + FULL_RUN: ${{ inputs.full }} run: | - echo "Base ref is $BASE_REF" - AFFECTED_UNIT=$(pnpm nx show projects --affected --target=test --base=origin/$BASE_REF --head=HEAD --select=projects --plain --exclude=cli,stories,eslint-angular) + if [ "$FULL_RUN" == "true" ]; then + echo "Running full (non-affected) test suite" + AFFECTED_UNIT=$(pnpm nx show projects --target=test --select=projects --plain --exclude=cli,stories,eslint-angular) + else + echo "Base ref is $BASE_REF" + AFFECTED_UNIT=$(pnpm nx show projects --affected --target=test --base=origin/$BASE_REF --head=HEAD --select=projects --plain --exclude=cli,stories,eslint-angular) + fi echo "Affected projects for UNIT: $AFFECTED_UNIT" if [ -z "$AFFECTED_UNIT" ]; then @@ -74,8 +89,56 @@ jobs: NODE_OPTIONS: "--max-old-space-size=5120" run: | xvfb-run --auto-servernum pnpm nx run ${{ matrix.project }}:test + - name: Upload coverage report + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: coverage-${{ matrix.project }} + path: coverage/${{ matrix.project }}/lcov.info + if-no-files-found: ignore + retention-days: 1 - name: Save nx cache if: ${{ success() }} uses: ./.github/actions/save-nx-cache with: cache-suffix: test-${{ matrix.project }} + + sonarcloud: + name: "SonarCloud Scan" + runs-on: ubuntu-latest + needs: [generate-affected-matrix, unit-tests] + if: ${{ needs.generate-affected-matrix.outputs.hasProjects == 'true' && always() && needs.unit-tests.result != 'cancelled' }} + permissions: + contents: read + pull-requests: read + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - name: Download all coverage artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: coverage-* + path: coverage-reports + - name: Merge coverage reports + run: | + mkdir -p coverage + echo "Artifact structure:" + find coverage-reports -type f -name 'lcov.info' 2>/dev/null || true + for dir in coverage-reports/coverage-*/; do + project_name=$(basename "$dir" | sed 's/^coverage-//') + lcov_file=$(find "$dir" -name 'lcov.info' -type f | head -1) + if [ -n "$lcov_file" ]; then + mkdir -p "coverage/${project_name}" + cp "$lcov_file" "coverage/${project_name}/lcov.info" + echo "Copied coverage for ${project_name}" + fi + done + echo "Coverage files found:" + find coverage -name 'lcov.info' -type f + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sonarcloud.io diff --git a/lib/content-services/karma.conf.js b/lib/content-services/karma.conf.js index a37f38f5e1..6880782c7c 100644 --- a/lib/content-services/karma.conf.js +++ b/lib/content-services/karma.conf.js @@ -56,7 +56,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/content-services'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/lib/core/karma.conf.js b/lib/core/karma.conf.js index 118b0f91a5..1e40fd315f 100644 --- a/lib/core/karma.conf.js +++ b/lib/core/karma.conf.js @@ -69,7 +69,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/core'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/lib/extensions/karma.conf.js b/lib/extensions/karma.conf.js index 4bdd4cd6e4..99b34c1729 100644 --- a/lib/extensions/karma.conf.js +++ b/lib/extensions/karma.conf.js @@ -23,7 +23,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/extensions'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/lib/insights/karma.conf.js b/lib/insights/karma.conf.js index 059cf7eb8a..8cf50ccf31 100644 --- a/lib/insights/karma.conf.js +++ b/lib/insights/karma.conf.js @@ -44,7 +44,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/insights'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/lib/insights/src/lib/diagram/models/chart/chart.model.spec.ts b/lib/insights/src/lib/diagram/models/chart/chart.model.spec.ts new file mode 100644 index 0000000000..e56e4854bc --- /dev/null +++ b/lib/insights/src/lib/diagram/models/chart/chart.model.spec.ts @@ -0,0 +1,148 @@ +/*! + * @license + * Copyright © 2005-2026 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 { Chart } from './chart.model'; + +describe('Chart Model', () => { + describe('constructor', () => { + it('should create with default values when no argument is provided', () => { + const chart = new Chart(); + expect(chart.labels).toEqual([]); + expect(chart.data).toEqual([]); + expect(chart.datasets).toEqual([]); + expect(chart.showDetails).toBe(false); + }); + + it('should populate properties from input object', () => { + const chart = new Chart({ + id: '1', + title: 'Test Chart', + titleKey: 'KEY', + labels: ['a', 'b'], + data: [1, 2], + datasets: [{ data: [1] }], + showDetails: true, + detailsTable: { key: 'value' }, + options: { responsive: true } + }); + + expect(chart.id).toBe('1'); + expect(chart.title).toBe('Test Chart'); + expect(chart.titleKey).toBe('KEY'); + expect(chart.labels).toEqual(['a', 'b']); + expect(chart.data).toEqual([1, 2]); + expect(chart.datasets).toEqual([{ data: [1] }]); + expect(chart.showDetails).toBe(true); + expect(chart.detailsTable).toEqual({ key: 'value' }); + expect(chart.options).toEqual({ responsive: true }); + }); + + it('should convert type and set icon for pieChart', () => { + const chart = new Chart({ type: 'pieChart' }); + expect(chart.type).toBe('pie'); + expect(chart.icon).toBe('pie_chart'); + }); + + it('should convert type and set icon for barChart', () => { + const chart = new Chart({ type: 'barChart' }); + expect(chart.type).toBe('bar'); + expect(chart.icon).toBe('equalizer'); + }); + + it('should convert type and set icon for line', () => { + const chart = new Chart({ type: 'line' }); + expect(chart.type).toBe('line'); + expect(chart.icon).toBe('show_chart'); + }); + + it('should convert type and set icon for table', () => { + const chart = new Chart({ type: 'table' }); + expect(chart.type).toBe('table'); + expect(chart.icon).toBe('web'); + }); + + it('should convert type and set icon for multiBarChart', () => { + const chart = new Chart({ type: 'multiBarChart' }); + expect(chart.type).toBe('multiBar'); + expect(chart.icon).toBe('poll'); + }); + + it('should convert type and set icon for processDefinitionHeatMap', () => { + const chart = new Chart({ type: 'processDefinitionHeatMap' }); + expect(chart.type).toBe('HeatMap'); + expect(chart.icon).toBe('share'); + }); + + it('should convert type and set icon for masterDetailTable', () => { + const chart = new Chart({ type: 'masterDetailTable' }); + expect(chart.type).toBe('masterDetailTable'); + expect(chart.icon).toBe('subtitles'); + }); + + it('should default to table type for unknown types', () => { + const chart = new Chart({ type: 'unknown' }); + expect(chart.type).toBe('table'); + expect(chart.icon).toBe('web'); + }); + }); + + describe('hasData', () => { + it('should return true when data is not empty', () => { + const chart = new Chart({ data: [1, 2, 3] }); + expect(chart.hasData()).toBe(true); + }); + + it('should return false when data is empty', () => { + const chart = new Chart({ data: [] }); + expect(chart.hasData()).toBe(false); + }); + + it('should return false when no data is provided', () => { + const chart = new Chart(); + expect(chart.hasData()).toBe(false); + }); + }); + + describe('hasDatasets', () => { + it('should return true when datasets is not empty', () => { + const chart = new Chart({ datasets: [{ data: [1] }] }); + expect(chart.hasDatasets()).toBe(true); + }); + + it('should return false when datasets is empty', () => { + const chart = new Chart({ datasets: [] }); + expect(chart.hasDatasets()).toBe(false); + }); + }); + + describe('hasZeroValues', () => { + it('should return true when all data values are zero', () => { + const chart = new Chart({ data: [0, 0, 0] }); + expect(chart.hasZeroValues()).toBe(true); + }); + + it('should return false when at least one value is non-zero', () => { + const chart = new Chart({ data: [0, 1, 0] }); + expect(chart.hasZeroValues()).toBe(false); + }); + + it('should return false when data is empty', () => { + const chart = new Chart({ data: [] }); + expect(chart.hasZeroValues()).toBe(false); + }); + }); +}); diff --git a/lib/process-services-cloud/karma.conf.js b/lib/process-services-cloud/karma.conf.js index a7d6664844..154eff3679 100644 --- a/lib/process-services-cloud/karma.conf.js +++ b/lib/process-services-cloud/karma.conf.js @@ -48,7 +48,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/process-services-cloud'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/lib/process-services/karma.conf.js b/lib/process-services/karma.conf.js index 03f0c6ac0f..27c5bf8de9 100644 --- a/lib/process-services/karma.conf.js +++ b/lib/process-services/karma.conf.js @@ -43,7 +43,7 @@ module.exports = function (config) { coverageReporter: { dir: join(__dirname, '../../coverage/process-services'), subdir: '.', - reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], + reporters: [{ type: 'html' }, { type: 'lcov' }, { type: 'text-summary' }, { type: 'text-summary', subdir: '.', file: 'summary.txt' }], check: { global: { statements: 75, diff --git a/nx.json b/nx.json index 45492e45a3..30bdd681b6 100644 --- a/nx.json +++ b/nx.json @@ -10,7 +10,8 @@ "cache": true }, "test": { - "cache": true + "cache": true, + "outputs": ["{workspaceRoot}/coverage/{projectName}"] }, "stylelint": { "cache": true diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000000..03cc6905f7 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,11 @@ +sonar.organization=alfresco +sonar.projectKey=Alfresco_alfresco-ng2-components + +sonar.sources=lib +sonar.tests=lib +sonar.test.inclusions=**/*.spec.ts +sonar.exclusions=**/node_modules/**,**/dist/**,**/*.spec.ts,**/*.mock.ts,**/mock/**,**/mocks/**,**/testing/**,**/stories/** + +sonar.javascript.lcov.reportPaths=coverage/core/lcov.info,coverage/content-services/lcov.info,coverage/extensions/lcov.info,coverage/insights/lcov.info,coverage/process-services/lcov.info,coverage/process-services-cloud/lcov.info + +sonar.sourceEncoding=UTF-8 From 5e0c52afcbd9a65afb514213e08cd4c67ebe40ea Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 20:10:52 +0200 Subject: [PATCH 15/31] AAE-50678 SonarCloud scan on daily cron instead of every develop push (#12184) * ci: change sonar-develop workflow to run on daily cron instead of every push to develop Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> * ci: add pull-requests: read permission to sonar-develop workflow Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: eromano <1030050+eromano@users.noreply.github.com> --- .github/workflows/sonar-develop.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonar-develop.yml b/.github/workflows/sonar-develop.yml index bb8d17828f..83bd79b0e7 100644 --- a/.github/workflows/sonar-develop.yml +++ b/.github/workflows/sonar-develop.yml @@ -1,9 +1,8 @@ name: "SonarCloud Full Scan (develop)" on: - push: - branches: - - develop + schedule: + - cron: '0 5 * * *' workflow_dispatch: {} concurrency: @@ -12,6 +11,7 @@ concurrency: permissions: contents: read + pull-requests: read jobs: full-unit-tests-and-sonar-scan: From cc39b19eea02c7c5b46f15d1c6542ccd99ff1287 Mon Sep 17 00:00:00 2001 From: Ehsan Rezaei Date: Wed, 19 Aug 2026 22:33:38 +0200 Subject: [PATCH 16/31] AAE-50665 Handling dynamic component destroy (#12183) * AAE-50665 Handling dynamic component destroy * AAE-50665 Code improvements --- .../base-screen-cloud.component.spec.ts | 198 ++++++++++++++++++ .../base-screen-cloud.component.ts | 38 ++-- ...art-process-screen-cloud.component.spec.ts | 124 ++++++++++- .../start-process-screen-cloud.component.ts | 10 +- .../screen-cloud.component.spec.ts | 126 ++++++++++- .../screen-cloud.component.ts | 78 +++---- 6 files changed, 507 insertions(+), 67 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts new file mode 100644 index 0000000000..f1135da80e --- /dev/null +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts @@ -0,0 +1,198 @@ +/*! + * @license + * Copyright © 2005-2026 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 { Component, ComponentRef, OnDestroy } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { BaseScreenCloudComponent } from './base-screen-cloud.component'; +import { provideScreen } from '../../../services/provide-screen'; +import { ScreenRenderingService } from '../../../services/screen-rendering.service'; + +@Component({ + selector: 'adf-cloud-test-dynamic-screen', + template: `
dynamic screen
` +}) +class TestDynamicScreenComponent implements OnDestroy { + destroyed = false; + + ngOnDestroy(): void { + this.destroyed = true; + } +} + +@Component({ + selector: 'adf-cloud-test-host-screen', + template: `` +}) +class TestHostScreenComponent extends BaseScreenCloudComponent { + setInputsCalls: ComponentRef[] = []; + subscribeToOutputsCalls: ComponentRef[] = []; + + get dynamicComponentRef(): ComponentRef | undefined { + return this.componentRef; + } + + get dynamicComponentRefSignalValue(): ComponentRef | undefined { + return this.componentRefChanged(); + } + + protected override setInputsForDynamicComponent(componentRef: ComponentRef): void { + this.setInputsCalls.push(componentRef); + } + + protected subscribeToOutputs(componentRef: ComponentRef): void { + this.subscribeToOutputsCalls.push(componentRef); + } +} + +/** Same host component, but without the `#container` anchor in its template. */ +@Component({ + selector: 'adf-cloud-test-host-screen-without-container', + template: `
` +}) +class TestHostScreenWithoutContainerComponent extends TestHostScreenComponent {} + +describe('BaseScreenCloudComponent', () => { + const screenId = 'test-screen'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestHostScreenComponent, TestHostScreenWithoutContainerComponent, TestDynamicScreenComponent], + providers: [provideScreen(screenId, TestDynamicScreenComponent)] + }); + }); + + describe('when a screenId is provided', () => { + let fixture: ComponentFixture; + let component: TestHostScreenComponent; + + beforeEach(() => { + fixture = TestBed.createComponent(TestHostScreenComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('screenId', screenId); + fixture.detectChanges(); + }); + + it('should create the dynamic component and expose it through the signal', () => { + expect(component.dynamicComponentRef).toBeDefined(); + expect(component.dynamicComponentRefSignalValue).toBe(component.dynamicComponentRef); + expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeTruthy(); + }); + + it('should wire inputs and outputs once, passing the created component reference', () => { + expect(component.setInputsCalls).toEqual([component.dynamicComponentRef!]); + expect(component.subscribeToOutputsCalls).toEqual([component.dynamicComponentRef!]); + }); + + it('should destroy the dynamic component reference on destroy', () => { + const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy').and.callThrough(); + + fixture.destroy(); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + + it('should run the ngOnDestroy hook of the dynamic component on destroy', () => { + const dynamicComponentInstance = component.dynamicComponentRef?.instance; + expect(dynamicComponentInstance?.destroyed).toBeFalse(); + + fixture.destroy(); + + expect(dynamicComponentInstance?.destroyed).toBeTrue(); + }); + + it('should clear the dynamic component reference and the signal on destroy', () => { + fixture.destroy(); + + expect(component.dynamicComponentRef).toBeUndefined(); + expect(component.dynamicComponentRefSignalValue).toBeUndefined(); + }); + + it('should destroy the dynamic component reference only once when ngOnDestroy runs again', () => { + const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy'); + + component.ngOnDestroy(); + component.ngOnDestroy(); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('when no screenId is provided', () => { + let fixture: ComponentFixture; + let component: TestHostScreenComponent; + + beforeEach(() => { + fixture = TestBed.createComponent(TestHostScreenComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should not create any dynamic component nor wire inputs and outputs', () => { + expect(component.dynamicComponentRef).toBeUndefined(); + expect(component.dynamicComponentRefSignalValue).toBeUndefined(); + expect(component.setInputsCalls).toEqual([]); + expect(component.subscribeToOutputsCalls).toEqual([]); + expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeNull(); + }); + + it('should not throw on destroy', () => { + expect(() => fixture.destroy()).not.toThrow(); + expect(component.dynamicComponentRef).toBeUndefined(); + }); + }); + + describe('when the container anchor is missing', () => { + let fixture: ComponentFixture; + let component: TestHostScreenWithoutContainerComponent; + + beforeEach(() => { + fixture = TestBed.createComponent(TestHostScreenWithoutContainerComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('screenId', screenId); + }); + + it('should not throw and should not create any dynamic component', () => { + expect(() => fixture.detectChanges()).not.toThrow(); + + expect(component.container).toBeUndefined(); + expect(component.dynamicComponentRef).toBeUndefined(); + expect(component.dynamicComponentRefSignalValue).toBeUndefined(); + }); + + it('should not wire inputs and outputs when no dynamic component was created', () => { + fixture.detectChanges(); + + expect(component.setInputsCalls).toEqual([]); + expect(component.subscribeToOutputsCalls).toEqual([]); + }); + + it('should not resolve any component type', () => { + const resolveComponentTypeSpy = spyOn(TestBed.inject(ScreenRenderingService), 'resolveComponentType').and.callThrough(); + + fixture.detectChanges(); + + expect(resolveComponentTypeSpy).not.toHaveBeenCalled(); + }); + + it('should not throw on destroy', () => { + fixture.detectChanges(); + + expect(() => fixture.destroy()).not.toThrow(); + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts index b00f85c4da..7da72468dc 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts @@ -15,20 +15,20 @@ * limitations under the License. */ -import { Component, ComponentRef, inject, Input, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core'; +import { Component, ComponentRef, inject, Input, OnDestroy, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core'; import { ScreenRenderingService } from '../../../services/screen-rendering.service'; @Component({ template: '' }) -export abstract class BaseScreenCloudComponent implements OnInit { +export abstract class BaseScreenCloudComponent implements OnInit, OnDestroy { @Input() screenId: string = ''; @ViewChild('container', { read: ViewContainerRef, static: true }) - container: ViewContainerRef; + container: ViewContainerRef | undefined; - protected componentRef: ComponentRef; + protected componentRef: ComponentRef | undefined; private readonly _componentRefChanged = signal | undefined>(undefined); protected readonly componentRefChanged = this._componentRefChanged.asReadonly(); protected readonly screenRenderingService = inject(ScreenRenderingService); @@ -37,17 +37,27 @@ export abstract class BaseScreenCloudComponent imple this.createDynamicComponent(); } - private createDynamicComponent(): void { - if (this.screenId) { - const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId }); - this.componentRef = this.container.createComponent(componentType); - this._componentRefChanged.set(this.componentRef); - this.setInputsForDynamicComponent(); - this.subscribeToOutputs(); - } + ngOnDestroy(): void { + this.componentRef?.destroy(); + this.componentRef = undefined; + this._componentRefChanged.set(undefined); } - protected setInputsForDynamicComponent(): void {} + private createDynamicComponent(): void { + if (!this.screenId || !this.container) { + return; + } - protected abstract subscribeToOutputs(): void; + const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId }); + const componentRef: ComponentRef = this.container.createComponent(componentType); + + this.componentRef = componentRef; + this._componentRefChanged.set(componentRef); + this.setInputsForDynamicComponent(componentRef); + this.subscribeToOutputs(componentRef); + } + + protected setInputsForDynamicComponent(_componentRef: ComponentRef): void {} + + protected abstract subscribeToOutputs(componentRef: ComponentRef): void; } diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts index 937d1ad536..da37f04b9e 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts @@ -16,6 +16,7 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, Input, OnDestroy, input, output } from '@angular/core'; import { StartProcessScreenCloudComponent } from './start-process-screen-cloud.component'; import { MockedTaskScreenCloudComponent } from '../../../../testing/start-process-screen-mock.component'; import { provideScreen } from '../../../services/provide-screen'; @@ -66,11 +67,11 @@ describe('StartProcessScreenCloudComponent', () => { it('should set appName', () => { const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance; - expect(screenInstance.appName()).toEqual(''); + expect(screenInstance.appName?.()).toEqual(''); const newValue = 'new-app-name'; fixture.componentRef.setInput('appName', newValue); fixture.detectChanges(); - expect(screenInstance.appName()).toEqual(newValue); + expect(screenInstance.appName?.()).toEqual(newValue); }); it('should set process definition id', () => { @@ -84,10 +85,125 @@ describe('StartProcessScreenCloudComponent', () => { it('should set resolvedValues', () => { const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance; - expect(screenInstance.resolvedValues()).toBeUndefined(); + expect(screenInstance.resolvedValues?.()).toBeUndefined(); const newValues = [new TaskVariableCloud({ id: 'new-id', name: 'new-name' })]; fixture.componentRef.setInput('resolvedValues', newValues); fixture.detectChanges(); - expect(screenInstance.resolvedValues()).toEqual(newValues); + expect(screenInstance.resolvedValues?.()).toEqual(newValues); + }); +}); + +@Component({ + selector: 'adf-cloud-destroy-tracking-screen', + template: `
screen
` +}) +class DestroyTrackingScreenComponent implements StartProcessScreenCloud, OnDestroy { + readonly appName = input(''); + processDefinitionId = input(''); + readonly resolvedValues = input(); + defaultStartProcessButtonsConfigurationChange = output(); + startProcessPayloadChanged = output(); + + destroyed = false; + + ngOnDestroy(): void { + this.destroyed = true; + } +} + +@Component({ + selector: 'adf-cloud-test-start-process-wrapper', + template: ` + @if (showScreen) { + + } + `, + imports: [StartProcessScreenCloudComponent] +}) +class TestStartProcessWrapperComponent { + @Input() screenId = ''; + showScreen = true; +} + +describe('StartProcessScreenCloudComponent - destroy', () => { + let fixture: ComponentFixture; + let component: TestStartProcessWrapperComponent; + const screenId = 'screen-1234-5678-121212-123456'; + + const getScreenInstance = (): DestroyTrackingScreenComponent => + fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent)).componentInstance; + + const destroyScreen = () => { + component.showScreen = false; + fixture.detectChanges(); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestStartProcessWrapperComponent], + providers: [provideScreen(screenId, DestroyTrackingScreenComponent)] + }); + + fixture = TestBed.createComponent(TestStartProcessWrapperComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('screenId', screenId); + fixture.detectChanges(); + }); + + it('should destroy the screen component when the host is destroyed', () => { + const screenInstance = getScreenInstance(); + expect(screenInstance.destroyed).toBeFalse(); + + destroyScreen(); + + expect(screenInstance.destroyed).toBeTrue(); + }); + + it('should remove the screen component from the DOM when the host is destroyed', () => { + expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeTruthy(); + + destroyScreen(); + + expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeNull(); + }); + + it('should create a new screen component instance when the host is re-created', () => { + const firstInstance = getScreenInstance(); + + destroyScreen(); + component.showScreen = true; + fixture.detectChanges(); + + const secondInstance = getScreenInstance(); + expect(secondInstance).not.toBe(firstInstance); + expect(secondInstance.destroyed).toBeFalse(); + expect(secondInstance.processDefinitionId()).toBe('definition-id'); + }); +}); + +describe('StartProcessScreenCloudComponent - without screenId', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [StartProcessScreenCloudComponent] + }); + fixture = TestBed.createComponent(StartProcessScreenCloudComponent); + }); + + it('should not create any screen component and should not throw', () => { + expect(() => fixture.detectChanges()).not.toThrow(); + expect(fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent))).toBeNull(); + }); + + it('should not throw when inputs change or on destroy', () => { + fixture.detectChanges(); + + expect(() => { + fixture.componentRef.setInput('appName', 'new-app-name'); + fixture.componentRef.setInput('resolvedValues', [new TaskVariableCloud({ id: 'id', name: 'name' })]); + fixture.detectChanges(); + }).not.toThrow(); + expect(() => fixture.destroy()).not.toThrow(); }); }); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts index 0f9c94dc81..ce069b6cdb 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, effect, input, output, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ComponentRef, effect, input, output, signal } from '@angular/core'; import { BaseScreenCloudComponent } from '../base-screen/base-screen-cloud.component'; import { MatCardModule } from '@angular/material/card'; import { CommonModule } from '@angular/common'; @@ -43,7 +43,7 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent { const componentRef = this.componentRefChanged(); - if (componentRef.instance && 'appName' in componentRef.instance) { + if (componentRef?.instance && 'appName' in componentRef.instance) { componentRef.setInput('appName', this.appName()); } }); @@ -56,9 +56,9 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent this.screenStartProcessPayloadChange.emit(payload)); - this.componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => { + protected subscribeToOutputs(componentRef: ComponentRef): void { + componentRef.instance.startProcessPayloadChanged.subscribe((payload) => this.screenStartProcessPayloadChange.emit(payload)); + componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => { this.showStartProcessButtons.set(config.show); this.disableStartProcessButton.emit(config.disable); }); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts index 3bf74e8816..e271a8a6e6 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts @@ -16,7 +16,7 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ScreenRenderingService } from '../../../services/screen-rendering.service'; import { TaskScreenCloudComponent } from './screen-cloud.component'; @@ -32,18 +32,22 @@ import { TaskScreenCloudComponent } from './screen-cloud.component'; ` }) -class TestComponent { +class TestComponent implements OnDestroy { @Input() taskId = ''; @Input() screenId = ''; @Input() rootProcessInstanceId = ''; @Output() taskCompleted = new EventEmitter(); - displayMode: string; + displayMode: string | undefined; + destroyed = false; onComplete() { this.taskCompleted.emit(); } switchToDisplayMode(newDisplayMode?: string) { this.displayMode = newDisplayMode; } + ngOnDestroy(): void { + this.destroyed = true; + } } @Component({ @@ -61,7 +65,7 @@ class TestComponent { }) class TestWrapperComponent { @Input() screenId = ''; - @ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent; + @ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent | undefined; onTaskCompleted() {} switchToDisplayMode(newDisplayMode?: string): void { if (this.adfCloudTaskScreen) { @@ -118,6 +122,118 @@ describe('TaskScreenCloudComponent', () => { component.switchToDisplayMode(); fixture.detectChanges(); - expect(component.adfCloudTaskScreen.switchToDisplayMode).toHaveBeenCalled(); + expect(component.adfCloudTaskScreen?.switchToDisplayMode).toHaveBeenCalled(); + }); +}); + +@Component({ + selector: 'adf-cloud-test-conditional-component', + template: ` + @if (showTaskScreen) { + + } + `, + imports: [TaskScreenCloudComponent] +}) +class TestConditionalWrapperComponent { + showTaskScreen = true; + onTaskCompleted() {} +} + +describe('TaskScreenCloudComponent - destroy', () => { + let fixture: ComponentFixture; + let component: TestConditionalWrapperComponent; + + const getDynamicComponentInstance = (): TestComponent => fixture.debugElement.query(By.directive(TestComponent)).componentInstance; + + const getTaskScreen = (): TaskScreenCloudComponent => fixture.debugElement.query(By.directive(TaskScreenCloudComponent)).componentInstance; + + const destroyTaskScreen = () => { + component.showTaskScreen = false; + fixture.detectChanges(); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TaskScreenCloudComponent, TestComponent, TestConditionalWrapperComponent] + }); + TestBed.inject(ScreenRenderingService).register({ ['test']: () => TestComponent }); + + fixture = TestBed.createComponent(TestConditionalWrapperComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should destroy the dynamic component when the task screen is destroyed', () => { + const dynamicComponentInstance = getDynamicComponentInstance(); + expect(dynamicComponentInstance.destroyed).toBeFalse(); + + destroyTaskScreen(); + + expect(dynamicComponentInstance.destroyed).toBeTrue(); + }); + + it('should remove the dynamic component from the DOM when the task screen is destroyed', () => { + expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeTruthy(); + + destroyTaskScreen(); + + expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeNull(); + }); + + it('should not emit outputs of the dynamic component after the task screen is destroyed', () => { + const onTaskCompletedSpy = spyOn(component, 'onTaskCompleted'); + const dynamicComponentInstance = getDynamicComponentInstance(); + + destroyTaskScreen(); + dynamicComponentInstance.taskCompleted.emit(); + + expect(onTaskCompletedSpy).not.toHaveBeenCalled(); + }); + + it('should not call the dynamic component when switching display mode after destroy', () => { + const taskScreen = getTaskScreen(); + const switchToDisplayModeSpy = spyOn(getDynamicComponentInstance(), 'switchToDisplayMode'); + + destroyTaskScreen(); + + expect(() => taskScreen.switchToDisplayMode('mode')).not.toThrow(); + expect(switchToDisplayModeSpy).not.toHaveBeenCalled(); + }); + + it('should create a new dynamic component instance when the task screen is re-created', () => { + const firstInstance = getDynamicComponentInstance(); + + destroyTaskScreen(); + component.showTaskScreen = true; + fixture.detectChanges(); + + const secondInstance = getDynamicComponentInstance(); + expect(secondInstance).not.toBe(firstInstance); + expect(secondInstance.destroyed).toBeFalse(); + expect(secondInstance.taskId).toBe('1'); + }); +}); + +describe('TaskScreenCloudComponent - without screenId', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TaskScreenCloudComponent] + }); + fixture = TestBed.createComponent(TaskScreenCloudComponent); + }); + + it('should not create any dynamic component and should not throw', () => { + expect(() => fixture.detectChanges()).not.toThrow(); + expect(fixture.debugElement.query(By.directive(TestComponent))).toBeNull(); + }); + + it('should not throw when switching display mode or destroying', () => { + fixture.detectChanges(); + + expect(() => fixture.componentInstance.switchToDisplayMode('mode')).not.toThrow(); + expect(() => fixture.destroy()).not.toThrow(); }); }); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts index 14ad0b5893..5069d34735 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts @@ -16,7 +16,7 @@ */ import { CommonModule } from '@angular/common'; -import { Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core'; +import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { UserTaskCustomUi } from './screen-cloud.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -102,65 +102,65 @@ export class TaskScreenCloudComponent extends BaseScreenCloudComponent): void { + if (this.taskId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskId')) { + componentRef.setInput('taskId', this.taskId); } - if (this.appName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'appName')) { - this.componentRef.setInput('appName', this.appName); + if (this.appName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'appName')) { + componentRef.setInput('appName', this.appName); } - if (this.screenId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'screenId')) { - this.componentRef.setInput('screenId', this.screenId); + if (this.screenId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'screenId')) { + componentRef.setInput('screenId', this.screenId); } - if (this.processInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'processInstanceId')) { - this.componentRef.setInput('processInstanceId', this.processInstanceId); + if (this.processInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'processInstanceId')) { + componentRef.setInput('processInstanceId', this.processInstanceId); } - if (this.taskName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskName')) { - this.componentRef.setInput('taskName', this.taskName); + if (this.taskName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskName')) { + componentRef.setInput('taskName', this.taskName); } - if (this.canClaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canClaimTask')) { - this.componentRef.setInput('canClaimTask', this.canClaimTask); + if (this.canClaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canClaimTask')) { + componentRef.setInput('canClaimTask', this.canClaimTask); } - if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canUnclaimTask')) { - this.componentRef.setInput('canUnclaimTask', this.canUnclaimTask); + if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canUnclaimTask')) { + componentRef.setInput('canUnclaimTask', this.canUnclaimTask); } - if (this.showCancelButton && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showCancelButton')) { - this.componentRef.setInput('showCancelButton', this.showCancelButton); + if (this.showCancelButton && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showCancelButton')) { + componentRef.setInput('showCancelButton', this.showCancelButton); } - if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'rootProcessInstanceId')) { - this.componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId); + if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'rootProcessInstanceId')) { + componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId); } - if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showNextTaskCheckbox')) { - this.componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox); + if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showNextTaskCheckbox')) { + componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox); } - if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'isNextTaskCheckboxChecked')) { - this.componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked); + if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(componentRef.instance, 'isNextTaskCheckboxChecked')) { + componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked); } } - protected override subscribeToOutputs(): void { - if (this.componentRef.instance?.taskSaved) { - this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit()); + protected override subscribeToOutputs(componentRef: ComponentRef): void { + if (componentRef.instance?.taskSaved) { + componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit()); } - if (this.componentRef.instance?.taskCompleted) { - this.componentRef.instance.taskCompleted + if (componentRef.instance?.taskCompleted) { + componentRef.instance.taskCompleted .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((openNextTask) => this.taskCompleted.emit(openNextTask)); } - if (this.componentRef.instance?.error) { - this.componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data)); + if (componentRef.instance?.error) { + componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data)); } - if (this.componentRef.instance?.claimTask) { - this.componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data)); + if (componentRef.instance?.claimTask) { + componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data)); } - if (this.componentRef.instance?.unclaimTask) { - this.componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data)); + if (componentRef.instance?.unclaimTask) { + componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data)); } - if (this.componentRef.instance?.cancelTask) { - this.componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data)); + if (componentRef.instance?.cancelTask) { + componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data)); } - if (this.componentRef.instance?.nextTaskCheckboxCheckedChanged) { - this.componentRef.instance.nextTaskCheckboxCheckedChanged + if (componentRef.instance?.nextTaskCheckboxCheckedChanged) { + componentRef.instance.nextTaskCheckboxCheckedChanged .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => this.nextTaskCheckboxCheckedChanged.emit(data)); } From beebf737b90fcd28352ff9e837ffefe19a78d9ea Mon Sep 17 00:00:00 2001 From: Shivangi Shree Date: Thu, 20 Aug 2026 16:15:29 +0530 Subject: [PATCH 17/31] [ACS-10230] Add role to search filter like logic, properties, date etc. (#12179) * [ACS-10230] Add role to search filter like logic, properties, date etc. * [ACS-10230] CR fixes --- .../search-filter-menu-card.component.html | 4 +++- .../search-filter-menu-card.component.scss | 5 +++++ .../search-filter-menu-card.component.spec.ts | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component.html b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component.html index 8167e8b14e..dd4ca6f210 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component.html +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component.html @@ -1,6 +1,8 @@
- +

+ +

diff --git a/lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts b/lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts index 5c9eda7791..d1a340e48c 100644 --- a/lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts +++ b/lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts @@ -17,7 +17,7 @@ /* eslint-disable @angular-eslint/component-selector */ -import { NgClass, NgIf } from '@angular/common'; +import { NgClass } from '@angular/common'; import { Component, ViewEncapsulation } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -47,7 +47,7 @@ import { WidgetComponent } from '../widget.component'; '(invalid)': 'event($event)', '(select)': 'event($event)' }, - imports: [NgClass, MatCheckboxModule, FormsModule, TranslatePipe, ErrorWidgetComponent, NgIf], + imports: [NgClass, MatCheckboxModule, FormsModule, TranslatePipe, ErrorWidgetComponent], encapsulation: ViewEncapsulation.None }) export class CheckboxWidgetComponent extends WidgetComponent {} diff --git a/lib/core/src/lib/form/components/widgets/error/error.component.scss b/lib/core/src/lib/form/components/widgets/error/error.component.scss index 026768b39d..2b57daf87a 100644 --- a/lib/core/src/lib/form/components/widgets/error/error.component.scss +++ b/lib/core/src/lib/form/components/widgets/error/error.component.scss @@ -10,12 +10,16 @@ } } +error-widget { + display: block; +} + .adf-error { display: flex; align-items: center; &-widget-container { - height: auto; + height: 40px; } &-animate { diff --git a/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss b/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss index a5f2b96287..91025303f0 100644 --- a/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss +++ b/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss @@ -1,6 +1,7 @@ .adf-hyperlink-widget { padding: 0.4375em 0; border-top: 0.8438em solid transparent; + margin-bottom: 20px; a { color: var(--mat-sys-primary); diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.scss b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.scss index 435d152e6e..242d172457 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.scss +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.scss @@ -43,6 +43,6 @@ @include mixins.adf-error-icon; } -.adf-container-widget .adf-multiline-text-widget .adf-form-field-input.adf-has-counter { - margin-bottom: 44px; +.adf-container-widget .adf-multiline-text-widget mat-form-field.adf-form-field-input.adf-has-counter { + margin-bottom: 20px; } diff --git a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss index 33e09becd6..843afbe1cd 100644 --- a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss +++ b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss @@ -9,7 +9,8 @@ } &-row-action { - margin-left: 10px; + margin-inline-start: 10px; + margin-block-end: 35px; } &-row-limit { diff --git a/lib/core/src/lib/styles/_mat-selectors.scss b/lib/core/src/lib/styles/_mat-selectors.scss index 31fa2b1208..dba941cafa 100644 --- a/lib/core/src/lib/styles/_mat-selectors.scss +++ b/lib/core/src/lib/styles/_mat-selectors.scss @@ -17,6 +17,7 @@ $mat-button: '.mat-mdc-button'; $mat-button-label: '.mdc-button__label'; $mat-form-field: '.mat-mdc-form-field'; $mat-form-field-wrapper: '.mat-mdc-text-field-wrapper'; +$mat-form-field-subscript-wrapper: '.mat-mdc-form-field-subscript-wrapper'; $mat-line-ripple: '.mdc-line-ripple'; $mat-form-field-prefix: '.mat-mdc-form-field-text-prefix'; $mat-form-field-suffix: '.mat-mdc-form-field-text-suffix'; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html index 0fea08b342..cb90cebccc 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html @@ -1,21 +1,26 @@
-
-
@@ -34,12 +39,15 @@ (contentModelFileHandler)="contentModelFormFileHandler($event)" (removeAttachFile)="onRemoveAttachFile($event)" /> -
- {{ 'FORM.FIELD.NO_FILE_ATTACHED' | translate }} -
- + @if (!hasFile && field.readOnly) { +
+ {{ 'FORM.FIELD.NO_FILE_ATTACHED' | translate }} +
+ }
- - +
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.html index 4bc5484ca1..a973a54176 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.html @@ -1,31 +1,23 @@
- +
- + @if (!previewState) { - + - - - - + [required]="dataTableLoadFailed ? ('FORM.FIELD.DATA_TABLE_LOAD_FAILED' | translate) : ''" + /> + } @else {
-
+ }
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss index 532599f8e3..ed9398c207 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss +++ b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss @@ -1,4 +1,5 @@ .adf-data-table-widget-failed-message { + display: block; margin: 10px; } diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.spec.ts index 2ec50a2d85..f770440650 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.spec.ts @@ -283,7 +283,8 @@ describe('DataTableWidgetComponent', () => { const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-data-table-widget-failed-message')); assertData(mockCountryColumns, []); - expect(failedErrorMsgElement).toBeNull(); + expect(failedErrorMsgElement).toBeTruthy(); + expect(failedErrorMsgElement.nativeElement.textContent.trim()).toBe(''); }); it('path points to single object with appropriate schema definition', () => { @@ -294,7 +295,8 @@ describe('DataTableWidgetComponent', () => { const failedErrorMsgElement = fixture.debugElement.query(By.css('.adf-data-table-widget-failed-message')); assertData(mockCountryColumns, [mockEuropeCountriesRows[1]]); - expect(failedErrorMsgElement).toBeNull(); + expect(failedErrorMsgElement).toBeTruthy(); + expect(failedErrorMsgElement.nativeElement.textContent.trim()).toBe(''); }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.ts index 15034eed76..a49c9ade68 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.ts @@ -27,7 +27,6 @@ import { NoContentTemplateDirective, EmptyContentComponent } from '@alfresco/adf-core'; -import { NgIf } from '@angular/common'; import { TranslatePipe } from '@ngx-translate/core'; import { FormCloudService } from '../../../services/form-cloud.service'; import { TaskVariableCloud } from '../../../models/task-variable-cloud.model'; @@ -36,7 +35,7 @@ import { DataTablePathParserHelper } from './helpers/data-table-path-parser.help @Component({ standalone: true, - imports: [NgIf, TranslatePipe, FormBaseModule, DataTableComponent, NoContentTemplateDirective, EmptyContentComponent], + imports: [TranslatePipe, FormBaseModule, DataTableComponent, NoContentTemplateDirective, EmptyContentComponent], selector: 'data-table', templateUrl: './data-table.widget.html', styleUrls: ['./data-table.widget.scss'], diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.html index 83b2abe75f..e3ed0116de 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.html @@ -4,11 +4,13 @@ [class.adf-readonly]="field.readOnly" [class.adf-left-label-input-container]="field.leftLabels" > -
- -
+ @if (field.leftLabels) { +
+ +
+ }
- -
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.spec.ts index 15ef00254b..6d6afa7d45 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.spec.ts @@ -141,8 +141,9 @@ describe('GroupCloudWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - expect(element.querySelector('.adf-error-text')).toBeTruthy(); - expect(element.querySelector('.adf-error-text').textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND'); + const errorMessages = element.querySelectorAll('.adf-error-text'); + expect(errorMessages.length).toBe(1); + expect(errorMessages[0].textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND'); }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.ts index f6555a01a7..c4c6a58a85 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/group/group-cloud.widget.ts @@ -16,7 +16,7 @@ */ import { Component, DestroyRef, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { ErrorWidgetComponent, WidgetComponent } from '@alfresco/adf-core'; +import { WidgetComponent } from '@alfresco/adf-core'; import { UntypedFormControl } from '@angular/forms'; import { filter } from 'rxjs/operators'; import { ComponentSelectionMode } from '../../../../types'; @@ -31,7 +31,7 @@ import { GroupCloudComponent } from '../../../../group/components/group-cloud.co @Component({ selector: 'group-cloud-widget', - imports: [CommonModule, TranslatePipe, ErrorWidgetComponent, GroupCloudComponent], + imports: [CommonModule, TranslatePipe, GroupCloudComponent], templateUrl: './group-cloud.widget.html', host: { '(click)': 'event($event)', diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.html index 399f01aca0..457aad503a 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.html @@ -1,10 +1,16 @@ -
-
- -
+
+ @if (field.leftLabels) { +
+ +
+ }
- -
- diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.spec.ts index 981033fdea..eb0d49d3bf 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.spec.ts @@ -171,8 +171,9 @@ describe('PeopleCloudWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - expect(element.querySelector('.adf-error-text')).toBeTruthy(); - expect(element.querySelector('.adf-error-text').textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND'); + const errorMessages = element.querySelectorAll('.adf-error-text'); + expect(errorMessages.length).toBe(1); + expect(errorMessages[0].textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND'); }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.ts index 976e4f3c0b..e849ff395e 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/people/people-cloud.widget.ts @@ -16,7 +16,7 @@ */ import { Component, DestroyRef, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { ErrorWidgetComponent, WidgetComponent } from '@alfresco/adf-core'; +import { WidgetComponent } from '@alfresco/adf-core'; import { UntypedFormControl } from '@angular/forms'; import { filter } from 'rxjs/operators'; import { ComponentSelectionMode } from '../../../../types'; @@ -27,13 +27,12 @@ import { ReactivePreselectionService } from '../reactive-preselection.service'; import { CommonModule } from '@angular/common'; import { TranslatePipe } from '@ngx-translate/core'; import { PeopleCloudComponent } from '../../../../people/components/people-cloud.component'; -import { MatFormFieldModule } from '@angular/material/form-field'; /* eslint-disable @angular-eslint/component-selector */ @Component({ selector: 'people-cloud-widget', - imports: [CommonModule, TranslatePipe, ErrorWidgetComponent, PeopleCloudComponent, MatFormFieldModule], + imports: [CommonModule, TranslatePipe, PeopleCloudComponent], templateUrl: './people-cloud.widget.html', host: { '(click)': 'event($event)', diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss index 3e2ae3f677..ae2d2c54f7 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss +++ b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss @@ -11,7 +11,6 @@ } &-radio-button-container-horizontal { - margin-bottom: 15px; display: flex; flex-flow: column wrap; align-items: flex-start; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.html index 6b8cb1a67d..ed73ace101 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.html @@ -4,51 +4,50 @@ >
- - - - {{file.name}} - - - + @if (hasFile) { + + + + {{file.name}} + @if (!field.readOnly) { + + } + + + }
-
- -
- -
{{ 'FORM.FIELD.NO_FILE_ATTACHED' | translate }}
+ @if ((!hasFile || multipleOption) && !field.readOnly) { +
+ +
+ } @if (!hasFile && field.readOnly) { +
{{ 'FORM.FIELD.NO_FILE_ATTACHED' | translate }}
+ }
- - +
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.spec.ts index 61e4537d8d..39f922fcfd 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/upload/upload-cloud.widget.spec.ts @@ -55,4 +55,12 @@ describe('UploadCloudWidgetComponent', () => { expect(eventSpy).toHaveBeenCalledWith(clickEvent); }); }); + + it('should render one reserved form field status area', () => { + widget.field = new FormFieldModel(new FormModel(), {}); + fixture.detectChanges(); + + const statusAreas = fixture.nativeElement.querySelectorAll('error-widget'); + expect(statusAreas.length).toBe(1); + }); }); diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.html b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.html index 759941de30..d903177dbb 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.html +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.html @@ -1,27 +1,37 @@
- - @if (label || required) { {{label}} } + + @if (label || required) { + {{ label }} + } - {{group.name}} - + title="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}" + > + {{ group.name }} + @if (!(group.readonly || readOnly)) { + + } - + - - -
- - {{group.name}} + data-automation-id="adf-cloud-group-autocomplete" + > + @if ((searchGroups$ | async)?.length) { + +
+ + {{ group.name }}
- + } @else { + + } - - {{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }} - + @if (searchGroupsControl.hasError('searchTypingError') && !searchLoading) { + + {{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }} + + } - -
- - -
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}
-
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}
+ @if (validationLoading) { + + } + @if (hasPreselectError() && !isValidationLoading()) { + + +
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}
-
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}
-
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}
-
- - -
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}
-
+ } + @if (searchGroupsControl.hasError('pattern')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }} +
+
+ } + @if (searchGroupsControl.hasError('maxlength')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }} +
+
+ } + @if (searchGroupsControl.hasError('minlength')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }} +
+
+ } + @if ((searchGroupsControl.hasError('required') || groupChipsCtrl.hasError('required')) && isDirty()) { + + +
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}
+
+ } + @if (searchGroupsControl.hasError('searchTypingError') && !this.isFocused) { + + +
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}
+
+ }
diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss index a9cdd5daad..7cca3876c9 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss @@ -57,10 +57,12 @@ } } -.adf-error-messages-container .adf-error-icon { - @include mixins.adf-error-icon; -} +.adf-error-messages-container { + .adf-error-icon { + @include mixins.adf-error-icon; + } -.adf-error-messages-container .adf-error { - animation: slide-down-fade-in 300ms cubic-bezier(0.55, 0, 0.55, 0.2); + .adf-error { + animation: slide-down-fade-in 300ms cubic-bezier(0.55, 0, 0.55, 0.2); + } } diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts index 09d88fe3f2..2c37fff1ca 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts @@ -27,6 +27,8 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatChipHarness } from '@angular/material/chips/testing'; import { MatIconHarness } from '@angular/material/icon/testing'; import { MatInputHarness } from '@angular/material/input/testing'; +import { MatFormField } from '@angular/material/form-field'; +import { MatProgressBar } from '@angular/material/progress-bar'; describe('GroupCloudComponent', () => { let loader: HarnessLoader; @@ -98,6 +100,22 @@ describe('GroupCloudComponent', () => { expect(await inputElement.getPlaceholder()).toEqual(''); }); + it('should use dynamic form field subscript sizing', () => { + fixture.detectChanges(); + + const formField = fixture.debugElement.query(By.directive(MatFormField)).componentInstance as MatFormField; + expect(formField.subscriptSizing).toBe('dynamic'); + }); + + it('should render validation progress inside the reserved status area', () => { + component.validationLoading = true; + fixture.detectChanges(); + + const progressBar = fixture.debugElement.query(By.directive(MatProgressBar)); + + expect(progressBar.parent.classes['adf-error-messages-container']).toBeTrue(); + }); + describe('Search group', () => { beforeEach(() => { fixture.detectChanges(); diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.html b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.html index e71ef489b3..c2b2633fe4 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.html +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.html @@ -4,10 +4,14 @@ class="adf-people-cloud adf-form-field-input" [class.adf-invalid]="hasError() && isDirty()" > - - {{label}} - - {{ title | translate }} + @if (!title) { + + {{ label }} + + } + @if (title) { + {{ title | translate }} + } {{ user | fullName }} - + @if (!(user.readonly || readOnly)) { + + } - + @if ((searchUsers$ | async)?.length) {
-
- {{ user | fullName : true }} +
+ {{ user | fullName: true }}
-
+ } @else { + + } - - {{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: searchedValue } }} - + @if (searchUserCtrl.hasError('searchTypingError') && !searchLoading) { + + {{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate: { userName: searchedValue } }} + + } - - -
- - -
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: validateUsersMessage } }}
-
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate : { pattern: getValidationPattern() } }}
-
- - -
- {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate : { requiredLength: getValidationMaxLength() } }} -
-
- - -
- {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate : { requiredLength: getValidationMinLength() } }} -
-
- - -
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}
-
- - -
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: searchedValue } }}
-
+
+ @if (validationLoading) { + + } + @if (showErrors) { + @if (hasPreselectError() && !isValidationLoading()) { + + +
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate: { userName: validateUsersMessage } }}
+
+ } + @if (searchUserCtrl.hasError('pattern')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }} +
+
+ } + @if (searchUserCtrl.hasError('maxlength')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }} +
+
+ } + @if (searchUserCtrl.hasError('minlength')) { + + +
+ {{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }} +
+
+ } + @if ((searchUserCtrl.hasError('required') || userChipsCtrl.hasError('required')) && isDirty()) { + + +
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}
+
+ } + @if (searchUserCtrl.hasError('searchTypingError') && !this.isFocused) { + + +
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate: { userName: searchedValue } }}
+
+ } + }
diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts index 3b99609dd0..98381d0c34 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.spec.ts @@ -27,6 +27,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatChipHarness } from '@angular/material/chips/testing'; import { MatInputHarness } from '@angular/material/input/testing'; import { MatFormFieldHarness } from '@angular/material/form-field/testing'; +import { MatProgressBar } from '@angular/material/progress-bar'; import { IdentityUserService } from '../services/identity-user.service'; describe('PeopleCloudComponent', () => { @@ -99,6 +100,19 @@ describe('PeopleCloudComponent', () => { expect(await inputField.getLabel()).toEqual('TITLE_KEY'); }); + it('should use dynamic form field subscript sizing by default', () => { + expect(component.formFieldSubscriptSizing).toBe('dynamic'); + }); + + it('should render validation progress inside the reserved status area', () => { + component.validationLoading = true; + fixture.detectChanges(); + + const progressBar = fixture.debugElement.query(By.directive(MatProgressBar)); + + expect(progressBar.parent.classes['adf-error-messages-container']).toBeTrue(); + }); + describe('Search user', () => { beforeEach(() => { fixture.detectChanges(); diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts index 5a11dd42e7..62a47a8af9 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.ts @@ -169,7 +169,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, AfterViewInit { * Material form field subscript sizing (fixed / dynamic) */ @Input() - formFieldSubscriptSizing: SubscriptSizing = 'fixed'; + formFieldSubscriptSizing: SubscriptSizing = 'dynamic'; /** * Show errors under the form field From 4d8d760e43676ad2d72f52e0d0495ac1a186ca0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 09:46:14 +0200 Subject: [PATCH 20/31] build(deps): bump the github-actions group across 2 directories with 9 updates (#12192) Bumps the github-actions group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.7` | `4.37.8` | | [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.37.7` | `4.37.8` | | [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.7` | `4.37.8` | | [Alfresco/alfresco-build-tools/.github/actions/send-teams-notification](https://github.com/alfresco/alfresco-build-tools) | `18.23.0` | `18.24.1` | | [crowdin/github-action](https://github.com/crowdin/github-action) | `2.17.0` | `2.17.1` | | [Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml](https://github.com/alfresco/alfresco-build-tools) | `18.23.0` | `18.24.1` | | [Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment](https://github.com/alfresco/alfresco-build-tools) | `18.23.0` | `18.24.1` | | [github/gh-aw-actions/setup](https://github.com/github/gh-aw-actions) | `0.86.2` | `0.87.2` | Bumps the github-actions group with 1 update in the /.github/actions/setup directory: [Alfresco/alfresco-build-tools/.github/actions/git-latest-tag](https://github.com/alfresco/alfresco-build-tools). Updates `github/codeql-action/init` from 4.37.7 to 4.37.8 - [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/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `github/codeql-action/autobuild` from 4.37.7 to 4.37.8 - [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/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.8 - [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/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `Alfresco/alfresco-build-tools/.github/actions/send-teams-notification` from 18.23.0 to 18.24.1 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/98bcfbe06aafffdc0e9a790f352602316f82303b...da99ab845e78301fcb0680d16cbc185a40a1938c) Updates `crowdin/github-action` from 2.17.0 to 2.17.1 - [Release notes](https://github.com/crowdin/github-action/releases) - [Commits](https://github.com/crowdin/github-action/compare/c7af9bc98b01694653031fef2a0dc6c7888ce9bc...8f01d54f70f1713ee3f09d82c2bbb2daeac28689) Updates `Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml` from 18.23.0 to 18.24.1 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/98bcfbe06aafffdc0e9a790f352602316f82303b...da99ab845e78301fcb0680d16cbc185a40a1938c) Updates `Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment` from 18.23.0 to 18.24.1 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/98bcfbe06aafffdc0e9a790f352602316f82303b...da99ab845e78301fcb0680d16cbc185a40a1938c) Updates `github/gh-aw-actions/setup` from 0.86.2 to 0.87.2 - [Release notes](https://github.com/github/gh-aw-actions/releases) - [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/gh-aw-actions/compare/6aab9e5b5c91c615506061f09bedd81a23babe3c...b304200a0ef4b3998673bfc7945acb08ab8c88b7) Updates `Alfresco/alfresco-build-tools/.github/actions/git-latest-tag` from 18.23.0 to 18.24.1 - [Release notes](https://github.com/alfresco/alfresco-build-tools/releases) - [Commits](https://github.com/alfresco/alfresco-build-tools/compare/98bcfbe06aafffdc0e9a790f352602316f82303b...da99ab845e78301fcb0680d16cbc185a40a1938c) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification dependency-version: 18.24.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: crowdin/github-action dependency-version: 2.17.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml dependency-version: 18.24.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment dependency-version: 18.24.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/gh-aw-actions/setup dependency-version: 0.87.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag dependency-version: 18.24.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/setup/action.yml | 2 +- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/notify-on-an-bdu-label.yml | 2 +- .github/workflows/pull-from-crowdin.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/stale-pr-cleanup.yml | 2 +- .github/workflows/supply-chain-pr-instructions.yml | 2 +- .github/workflows/supply-chain-review.lock.yml | 14 +++++++------- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 436e1fa0aa..00f35a4057 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -35,7 +35,7 @@ runs: - name: get latest tag sha if: ${{ inputs.full-setup == 'true' }} id: tag-sha - uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 + uses: Alfresco/alfresco-build-tools/.github/actions/git-latest-tag@da99ab845e78301fcb0680d16cbc185a40a1938c # v18.24.1 - name: load "NPM TAG" if: ${{ inputs.full-setup == 'true' }} id: set-npm-tag diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9a2c2a18d4..afc0c5eeed 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v3.29.5 # 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@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 + uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v3.29.5 # ℹ️ 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@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v3.29.5 diff --git a/.github/workflows/notify-on-an-bdu-label.yml b/.github/workflows/notify-on-an-bdu-label.yml index cafc0dff62..2dec383118 100644 --- a/.github/workflows/notify-on-an-bdu-label.yml +++ b/.github/workflows/notify-on-an-bdu-label.yml @@ -53,7 +53,7 @@ jobs: - name: Send Teams notification if: steps.check_label_timing.outputs.should_notify == 'true' - uses: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 + uses: Alfresco/alfresco-build-tools/.github/actions/send-teams-notification@da99ab845e78301fcb0680d16cbc185a40a1938c # v18.24.1 with: webhook-url: ${{ secrets.TEAMS_NOTIFICATION_ADF_BDU_WEBHOOK }} skip_checkout: true diff --git a/.github/workflows/pull-from-crowdin.yml b/.github/workflows/pull-from-crowdin.yml index 6e6feb074d..51ff36ad06 100644 --- a/.github/workflows/pull-from-crowdin.yml +++ b/.github/workflows/pull-from-crowdin.yml @@ -29,7 +29,7 @@ jobs: ref: develop token: ${{ steps.app-token.outputs.token }} - name: Pull translations from Crowdin - uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0 + uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2.17.1 with: skip_ref_checkout: true upload_sources: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3c66cb632..a5afc3eae1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Push Source Files to Crowdin - uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0 + uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2.17.1 with: upload_sources: true upload_sources_args: --delete-obsolete diff --git a/.github/workflows/stale-pr-cleanup.yml b/.github/workflows/stale-pr-cleanup.yml index e14f9155c7..7a970ff8f5 100644 --- a/.github/workflows/stale-pr-cleanup.yml +++ b/.github/workflows/stale-pr-cleanup.yml @@ -11,4 +11,4 @@ permissions: jobs: stale-pr-cleanup: - uses: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 + uses: Alfresco/alfresco-build-tools/.github/workflows/stale-pr-cleanup.yml@da99ab845e78301fcb0680d16cbc185a40a1938c # v18.24.1 diff --git a/.github/workflows/supply-chain-pr-instructions.yml b/.github/workflows/supply-chain-pr-instructions.yml index 5c5166f57f..92340b418c 100644 --- a/.github/workflows/supply-chain-pr-instructions.yml +++ b/.github/workflows/supply-chain-pr-instructions.yml @@ -16,7 +16,7 @@ jobs: permissions: pull-requests: write steps: - - uses: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment@98bcfbe06aafffdc0e9a790f352602316f82303b # v18.23.0 + - uses: Alfresco/alfresco-build-tools/.github/actions/github-upsert-comment@da99ab845e78301fcb0680d16cbc185a40a1938c # v18.24.1 with: comment-identifier: supply-chain-review-instructions comment-body: | diff --git a/.github/workflows/supply-chain-review.lock.yml b/.github/workflows/supply-chain-review.lock.yml index 8a114a76b5..05de38a487 100644 --- a/.github/workflows/supply-chain-review.lock.yml +++ b/.github/workflows/supply-chain-review.lock.yml @@ -41,7 +41,7 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 +# - github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 @@ -125,7 +125,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -494,7 +494,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1128,7 +1128,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1404,7 +1404,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1653,7 +1653,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1732,7 +1732,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + uses: github/gh-aw-actions/setup@b304200a0ef4b3998673bfc7945acb08ab8c88b7 # v0.87.2 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} From 9f37f22702371ca7c7b48cd200580b0f9a045e9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 08:03:57 +0000 Subject: [PATCH 21/31] build(deps): bump @cspell/eslint-plugin from 10.0.0 to 10.0.1 (#12191) Bumps [@cspell/eslint-plugin](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell-eslint-plugin) from 10.0.0 to 10.0.1. - [Release notes](https://github.com/streetsidesoftware/cspell/releases) - [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell-eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/streetsidesoftware/cspell/commits/v10.0.1/packages/cspell-eslint-plugin) --- updated-dependencies: - dependency-name: "@cspell/eslint-plugin" dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Domenico Sibilio --- package.json | 2 +- pnpm-lock.yaml | 340 +++++++++++++++++++++++++------------------------ 2 files changed, 174 insertions(+), 168 deletions(-) diff --git a/package.json b/package.json index 790f5677b8..17da1c277e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@angular/platform-browser-dynamic": "20.3.27", "@angular/router": "20.3.27", "@apollo/client": "3.13.1", - "@cspell/eslint-plugin": "10.0.0", + "@cspell/eslint-plugin": "10.0.1", "@mat-datetimepicker/core": "16.0.1", "@ngx-translate/core": "17.0.0", "angular-oauth2-oidc": "19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e695136426..1f5dd58083 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,8 +69,8 @@ importers: specifier: 3.13.1 version: 3.13.1(graphql-ws@6.0.8(graphql@16.14.0)(ws@8.21.0))(graphql@16.14.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@cspell/eslint-plugin': - specifier: 10.0.0 - version: 10.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) + specifier: 10.0.1 + version: 10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0)) '@mat-datetimepicker/core': specifier: 16.0.1 version: 16.0.1(@angular/cdk@20.2.14(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/material@20.2.14(7fc7dc26a5364b20a95267fc7b4f10c7)) @@ -1288,28 +1288,28 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@cspell/cspell-bundled-dicts@10.0.0': - resolution: {integrity: sha512-ci410HEkng2582oOjlRHQtlGXwh+rUC/mVcN9dObLHpKhvPgzn2S6vT56pARstxxZpcCUG/oLhn3dCqdJlVzmA==} + '@cspell/cspell-bundled-dicts@10.0.1': + resolution: {integrity: sha512-WvkSDNX4Uyyj/ZgbPO6L38iFNMfK1EqsH1FteRiI2qLz6QZMXRFrIt12OqiWIplzZDDaVpBH9FCJOPJll0fjCQ==} engines: {node: '>=22.18.0'} - '@cspell/cspell-performance-monitor@10.0.0': - resolution: {integrity: sha512-2vMh2pLt2dg/ArYvWjMP4v9HCm0pRhONsEJyc8oHdZyOYvX7trixX894I0M39+VBf3yWtPCEgYRh1UDXNIZRig==} + '@cspell/cspell-performance-monitor@10.0.1': + resolution: {integrity: sha512-9tVcHXwRnbazUv4WSG0h3MqV4+LgmLNgSALAQUflPPW0EMxTf7C4Dmv9cgxJyCEQrdnVKCr58nPPaahhz9LJUg==} engines: {node: '>=22.18.0'} - '@cspell/cspell-pipe@10.0.0': - resolution: {integrity: sha512-qcgHhQvtEX8LSwIVsWrdUgiGim52lN3jT+ghlkdp72v+nBcGKsS2frEKTmbGLug+xcqppkzs6Q6VmsFp1MGtfA==} + '@cspell/cspell-pipe@10.0.1': + resolution: {integrity: sha512-HPeXMD9AZ3V/qPkvQaPcak+C7cJ2z7JTHN8smd6J8L2aThLRky2cHc2OyeaHPSHB7WA47b4z2n5u5nawZhv5VQ==} engines: {node: '>=22.18.0'} - '@cspell/cspell-resolver@10.0.0': - resolution: {integrity: sha512-8H+IUDB7SmrpcRugQ5f55qG81ZShk6nQRk+natLz41TEY98D8/LCmjHEkh/vhDPph9pVJmNUp7JcM2E1UHEa2g==} + '@cspell/cspell-resolver@10.0.1': + resolution: {integrity: sha512-PIzkZHD1fGUQx1XteK2d1iQ0Mzq/maYcoB4jkvAiiR6WqP3MWYNKFdI9z+R5pOq5KgMfW+5Ig1q0oSR6h8irlA==} engines: {node: '>=22.18.0'} - '@cspell/cspell-service-bus@10.0.0': - resolution: {integrity: sha512-V7eigqg/TOoKwNK4Q18wr9KGxA8U5SFcoWVS8RyAxv4mQ+yNKHhvHEbRBifjPbQDer66afOrclb2UbqkIy2SOw==} + '@cspell/cspell-service-bus@10.0.1': + resolution: {integrity: sha512-y6NcIGP2IdXaBL4PVH8vxsr7K27wzz3Ech87UtUtrDSXAiVEOvXgAIknEOUVp59rTlUE8Rn4IRURC6f/hgMyfw==} engines: {node: '>=22.18.0'} - '@cspell/cspell-types@10.0.0': - resolution: {integrity: sha512-IQA++Idqb8fZzkCbHq3+T+9yG9WpeaBxomOrG2KcR/Pj0CgnovzuApYKL2cc35UWLePboKinMeqEPiweFpHVug==} + '@cspell/cspell-types@10.0.1': + resolution: {integrity: sha512-kLgLShnWADDVreKC63pBrWkcvxgZzFIfO34Jhx/SWfuOIA3cD8AXT+HjyuLfoGJ7mUb58hv2kUziKzEy4INb1w==} engines: {node: '>=22.18.0'} '@cspell/dict-ada@4.1.1': @@ -1324,11 +1324,11 @@ packages: '@cspell/dict-bash@4.2.3': resolution: {integrity: sha512-ljUZoKHbDqw5Sx0qpL2qTUlmkmr+vhZH/sCNrNaBZKTbdgiswErSnIF1jRbGmEitJNxHRHWsuZyVgnTGfVO1Yw==} - '@cspell/dict-companies@3.2.11': - resolution: {integrity: sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==} + '@cspell/dict-companies@3.2.12': + resolution: {integrity: sha512-mjiz/N3zWOCsz5VfwMUydSl7uW0OU9H2PnbCNc3RV44Vj6Q59CSp6EYGSGZQxrXU1gpsuZUrwr6QCjNjFOOg5A==} - '@cspell/dict-cpp@7.0.2': - resolution: {integrity: sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==} + '@cspell/dict-cpp@7.1.0': + resolution: {integrity: sha512-rcjycobioQUd9Jm/Y1n8U+sq6ytpZ0iV8AYIo+vyEFfutC5x7g6hL6Fh3bCdh6IporGHRqfEutGK/4sfopD2ZA==} '@cspell/dict-cryptocurrencies@5.0.5': resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} @@ -1342,8 +1342,8 @@ packages: '@cspell/dict-dart@2.3.2': resolution: {integrity: sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==} - '@cspell/dict-data-science@2.0.14': - resolution: {integrity: sha512-jl6Ds4u5u5JT+yY30pWQpAbdCHfy3lCcNkLbpL/AZKoUaLEoXbaYsps9xQtvD7DyaiXxiLZkdH2yHHXtoFtZyg==} + '@cspell/dict-data-science@2.0.16': + resolution: {integrity: sha512-M72mxv5asuAnORurz4iXRJ+Tw9XBq6eu7D2Ne7biP0Z1RciKGNxXWu9JycA/KlVvK1hAlKj/fANlXhuEWpXKFg==} '@cspell/dict-django@4.1.6': resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==} @@ -1357,14 +1357,14 @@ packages: '@cspell/dict-elixir@4.0.8': resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} - '@cspell/dict-en-common-misspellings@2.1.12': - resolution: {integrity: sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==} + '@cspell/dict-en-common-misspellings@2.2.0': + resolution: {integrity: sha512-5PmCHv+AhY0LVNo3bE1FdRKMmw1esKj83GPwLymnVPYNtlI2Jf6y27EjC0azg4zny5keWmku0GQiMf55Fi+qeA==} - '@cspell/dict-en-gb-mit@3.1.24': - resolution: {integrity: sha512-Oowb/Uzkh7OmDRdCcETzMc9imEb4IpLlHJXoYjX8A8DS2X/54gqSjI915JFB8hKtFjBko5OM0BLQ+6cZhFEMmQ==} + '@cspell/dict-en-gb-mit@3.1.25': + resolution: {integrity: sha512-zGODptk24CMrXi49ieG2SUm94CKxEsVF0dYNF+1ZYH0MSsQDZ/PKDlrrbvtBqSupKdPSj0Z9sjOmMNfHHW9ZSg==} - '@cspell/dict-en_us@4.4.35': - resolution: {integrity: sha512-xWpxBCc/FzzMMo/A+0qwARVaIIhR0Ql8yhhv4rvsvg+GfQF+LG9yzg2GwTM5N2rjvzmM3nKuR9zxFZq2I6fJSg==} + '@cspell/dict-en_us@4.4.36': + resolution: {integrity: sha512-2yOhI/+7d1DbfvMljGW4jw8pLqDEsVmnvUXBOCFXtLU2BWgQkrqOJDCNseYjEiEbTp0OtdrWEWWPFSP1TNugQw==} '@cspell/dict-filetypes@3.0.18': resolution: {integrity: sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==} @@ -1387,8 +1387,8 @@ packages: '@cspell/dict-git@3.1.0': resolution: {integrity: sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==} - '@cspell/dict-golang@6.0.26': - resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==} + '@cspell/dict-golang@6.0.27': + resolution: {integrity: sha512-rRDb2JL8EefaezHGTEclKXCs4eMOS0q/datk94Lm7KQOhlGlzS9FDVZiR1/guh0o+iJCmejQuf5NR2FQeQSWsg==} '@cspell/dict-google@1.0.9': resolution: {integrity: sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==} @@ -1399,8 +1399,8 @@ packages: '@cspell/dict-html-symbol-entities@4.0.5': resolution: {integrity: sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==} - '@cspell/dict-html@4.0.15': - resolution: {integrity: sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==} + '@cspell/dict-html@4.0.16': + resolution: {integrity: sha512-9B6/Cpb5YVcYgsEAlKudun1yd4ONllYS/ZibKLYea2apPR+l2vQdARnPrP9kTqa7NUNfRKl7m9Fb6k9rjimnow==} '@cspell/dict-java@5.0.12': resolution: {integrity: sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==} @@ -1408,8 +1408,8 @@ packages: '@cspell/dict-julia@1.1.1': resolution: {integrity: sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==} - '@cspell/dict-k8s@1.0.12': - resolution: {integrity: sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==} + '@cspell/dict-k8s@1.0.13': + resolution: {integrity: sha512-ELGkS13k7K/NEfVimBSrxVTfqXvOF/Kvxj4I62YxRm8bvHbfoXgrGaOx28lPiNRz+dmu+yYtvuXbnURKtYbC6g==} '@cspell/dict-kotlin@1.1.1': resolution: {integrity: sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==} @@ -1426,22 +1426,22 @@ packages: '@cspell/dict-makefile@1.0.5': resolution: {integrity: sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==} - '@cspell/dict-markdown@2.0.17': - resolution: {integrity: sha512-H8bAxih6U8NOnSPL7R8My+tqjaB4tmnJTjERuz4zYqmf+cH+5xshX3UVgKlwWFcyjsYfv/zEDuRdMctQv1q6HQ==} + '@cspell/dict-markdown@2.0.18': + resolution: {integrity: sha512-KLRSwVrwKDz4n7bl2XQjzvaBZbGgx5QKkP5Ok1b24xaO3TpXsLhAbAn1mItvFlI2tF6Rkt83iYjQcYppywuf5Q==} peerDependencies: '@cspell/dict-css': ^4.1.2 - '@cspell/dict-html': ^4.0.15 + '@cspell/dict-html': ^4.0.16 '@cspell/dict-html-symbol-entities': ^4.0.5 '@cspell/dict-typescript': ^3.2.3 - '@cspell/dict-monkeyc@1.0.12': - resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==} + '@cspell/dict-monkeyc@1.1.0': + resolution: {integrity: sha512-mc/hgvSy/emOIYtc8kuVEaLUlnaERunfAubfJ5plUXq6s0A69bcukJzUzSt9C0UTCwS28641ILRPv80m3L9QbA==} '@cspell/dict-node@5.0.9': resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} - '@cspell/dict-npm@5.2.41': - resolution: {integrity: sha512-To3xsfRmMBYVXtWVEdUgV35M9a/JZ54dSuoY6m6D3uHKKL3I326Wmy4xifZ3PU8MQaWhyEH7zbIcUEtKwTQMcA==} + '@cspell/dict-npm@5.2.46': + resolution: {integrity: sha512-Z5GG59c17UEk6BPZ3lVD/++GvPBI+O05OTGm4sRA/0I/qszbstuYRw+j2//5f8SWzOFXKaGYI8kygwKX9EQZ9Q==} '@cspell/dict-php@4.1.1': resolution: {integrity: sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==} @@ -1452,14 +1452,14 @@ packages: '@cspell/dict-public-licenses@2.0.16': resolution: {integrity: sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==} - '@cspell/dict-python@4.2.27': - resolution: {integrity: sha512-Rj6xQgYS4X6ienjgAZF+njA0GRY4oSPouJWv0vfikCTn6EWlfk0V6Dy1HP3Migj1O+IC2NmespgVq+BZNSp8OA==} + '@cspell/dict-python@4.4.0': + resolution: {integrity: sha512-49Eju/a97V6ExAeMJDM2Tk4jsYr0pm+kQzqQrKLAZdrRd6VrLIgseI481EQSmgoVkPPdsuwK1xj9ZlpDQIF4qg==} '@cspell/dict-r@2.1.1': resolution: {integrity: sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==} - '@cspell/dict-ruby@5.1.1': - resolution: {integrity: sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==} + '@cspell/dict-ruby@5.1.2': + resolution: {integrity: sha512-f6Slf11N91ittf71AWlfVVG9GZPezVRBfcMPCTNTWhUsY/VEspkBRZYDhPXjV4df3jqHy5YJs8nlsFcFVF/bMA==} '@cspell/dict-rust@4.1.2': resolution: {integrity: sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==} @@ -1470,8 +1470,8 @@ packages: '@cspell/dict-shell@1.2.0': resolution: {integrity: sha512-PVctvT22lJ49niMiakO8xieY7ELCAzjSqhejWR7bAMb5AZ9F4WDEs+XdGMnoVHWeXq7K5rcepLPmEJb+37zzIw==} - '@cspell/dict-software-terms@5.2.2': - resolution: {integrity: sha512-0CaYd6TAsKtEoA7tNswm1iptEblTzEe3UG8beG2cpSTHk7afWIVMtJLgXDv0f/Li67Lf3Z1Jf3JeXR7GsJ2TRw==} + '@cspell/dict-software-terms@5.4.1': + resolution: {integrity: sha512-tY88gnOWj2ax6h+i7BjU7dIH1f3fvY4WypS0a+TpjBgXC318AdOMmy0zK1SzxGNOjhWJfCeCZNVoLFM/DuOG4g==} '@cspell/dict-sql@2.2.1': resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} @@ -1482,8 +1482,8 @@ packages: '@cspell/dict-swift@2.0.6': resolution: {integrity: sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==} - '@cspell/dict-terraform@1.1.3': - resolution: {integrity: sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==} + '@cspell/dict-terraform@1.1.4': + resolution: {integrity: sha512-Ere42ilvMFvQA4GlcN0OKlruMPR6EsvaB+iTHzj2xc+NJGRK64V7yApUcWrOrSgTiM/vhWXPIsK3OMfiAiNdmA==} '@cspell/dict-typescript@3.2.3': resolution: {integrity: sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==} @@ -1494,30 +1494,30 @@ packages: '@cspell/dict-zig@1.0.0': resolution: {integrity: sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==} - '@cspell/dynamic-import@10.0.0': - resolution: {integrity: sha512-fMqu/5Ma1Q5ZCR/Par+Q4pvaTKmx5pKZzQmkwld2hNounVdk2OaIPM9MzpNn6I1mLk5J+wTnIZmfcWNAzNP9aQ==} + '@cspell/dynamic-import@10.0.1': + resolution: {integrity: sha512-mP1gdq00aIcH8HxNMqnH11X6BKxLcneDtFgl/ecjIKnaGKwi44m8AndP5Kr4ODaYdl8UUw9O3dJh7KaQXnLHZQ==} engines: {node: '>=22.18.0'} - '@cspell/eslint-plugin@10.0.0': - resolution: {integrity: sha512-NXXismQgYtAg9w399Oa57TYZpm1u9VLX5LZADjgYz0ZwR/9FVwbOP2yiUisQXODCTAP+fnWGJPmYRiHmSYrW9Q==} + '@cspell/eslint-plugin@10.0.1': + resolution: {integrity: sha512-VgRVWIWyM5bz2d7eC3/F4ON1fhMdrFB0R6slXmJewDDbJMDloSsMsWCSktiFET3a0TX/DTjOGGDzBN9tqGDGPw==} engines: {node: '>=22.18.0'} peerDependencies: eslint: ^8 || ^9 || ^10 - '@cspell/filetypes@10.0.0': - resolution: {integrity: sha512-UP57j9yrDtlCHpFxc/eGho1m8DP5olfu9KRWwd5fiqL9nMSE2rUJtPzQyvqmDwO5bVZt3B+fTVdo4gxuiqw25A==} + '@cspell/filetypes@10.0.1': + resolution: {integrity: sha512-Z5S35giU5IW49fBBq6BksUbE8PC4IYPfaKuwl5Nl9jkf/OkAKiBmCowKX45NzRUQInwK/GSqqIUifrNeI6LdLw==} engines: {node: '>=22.18.0'} - '@cspell/rpc@10.0.0': - resolution: {integrity: sha512-QrpOZMwz2pAjvl6Hky2PauYoMpLCASn3osjn7uKUbgFV70sahyj6tmx4rRgRX7vHu2WQLZev+YsuO4EujiBDOg==} + '@cspell/rpc@10.0.1': + resolution: {integrity: sha512-axSRKv3zEAmBm66iD/FV/MPmE4/Yf7c3PZiwTW894Yd3iEhtn3KPKeTrqQ2/tDrhB1Z2qTsap/Hue0MK4o5WXg==} engines: {node: '>=22.18.0'} - '@cspell/strong-weak-map@10.0.0': - resolution: {integrity: sha512-JRsato0s2IjYdsng+AGL6oAqgZVQgih5aWKdmxs21H6EdhMaoFDmRE5kXm/RT5a6OMdtnzQM9DqeToqBChWIOQ==} + '@cspell/strong-weak-map@10.0.1': + resolution: {integrity: sha512-lenN1DVyPi8nJLSMSJJ670ddTjyiruLueuSZO1qLcxBqUhgxDt/mALu9N/1m6WdOVcg6m/5cLiZVg2KOo2UzRw==} engines: {node: '>=22.18.0'} - '@cspell/url@10.0.0': - resolution: {integrity: sha512-q+0pHQ8DbqjemyaOn/mTtBRbCuKDqhnsVbZ6J9zkTsxPgMpccjy0s5oLXwomfrrxMRBH+UcbERwtUmE+SbnoIQ==} + '@cspell/url@10.0.1': + resolution: {integrity: sha512-abYYgI29wJhWIfWTYrYuzRYDcHQUQ1N5ylnhxYn1NJnIQMqUWGLbDmt12JABtZ+R6h6UNatQrS7rhP86etvJyQ==} engines: {node: '>=22.18.0'} '@cspotcode/source-map-support@0.8.1': @@ -5183,8 +5183,8 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - comment-json@4.6.2: - resolution: {integrity: sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==} + comment-json@5.0.0: + resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} engines: {node: '>= 6'} comment-parser@1.4.7: @@ -5323,36 +5323,36 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - cspell-config-lib@10.0.0: - resolution: {integrity: sha512-HWK7SRnJ3N/kOThw/uzmXmQYCzBxu58Jkq2hHyte1voDl118BeNFoaNRWMpYdHbBi3kCj8gaZu8wGtm+Zmdhxw==} + cspell-config-lib@10.0.1: + resolution: {integrity: sha512-hMpo/0j6k7pbiqrLDOLJKD2IGP9XwhjKf2miiM6p84Xeo4nyuFZaxxDCQ68R851HSYFrrdltgpoipMbj1h2Tnw==} engines: {node: '>=22.18.0'} - cspell-dictionary@10.0.0: - resolution: {integrity: sha512-KubSoEAJO+77KPSSWjoLCz0+MIWVNq3joGTSyxucAZrBSJD64Y1O4BHHr1aj6XHIZwXhWWNScQlrQR3OcIulng==} + cspell-dictionary@10.0.1: + resolution: {integrity: sha512-3cZ659vgsZWkzGQJR/sNqGDVt/OnvTSieLKI76V++4t1bHJfochb9ZrrwsuMsb1VPGiyqClUP1/O6WrefF/FVg==} engines: {node: '>=22.18.0'} - cspell-glob@10.0.0: - resolution: {integrity: sha512-bXS35fMcA9X7GEkfnWBfoPd/vTnxxfXW+YHt6tWxu5fejfs00qUbjWp1oLC9FxRaXWxIkfsYp2mi1k1jYl4RVw==} + cspell-glob@10.0.1: + resolution: {integrity: sha512-7bII9J3aSSpZDwhx7w+zfQXbMxHZQ3be0ilUp5bHrsjz6o07v/NqOHMGcwKdPn1sw2dxDz9sv057xE5pqXnSdw==} engines: {node: '>=22.18.0'} - cspell-grammar@10.0.0: - resolution: {integrity: sha512-49udtYzkcCYEIDJbFOb4IwiAJebOYZnYvG6o6Ep19Tq0Xwjk7i4vxUprNiFNDCWFbcbJRPE9cpwQUVwp5WFGLw==} + cspell-grammar@10.0.1: + resolution: {integrity: sha512-xC9AFYmaI9wsO//a7S5tdDGKGJVD5UEEsTg+Up2fi7lPfXIryisYmV6tePNL1SEg0idYss4ja8LUZ3Mib09BjQ==} engines: {node: '>=22.18.0'} hasBin: true - cspell-io@10.0.0: - resolution: {integrity: sha512-NQCAUhx9DwKApxPuFl7EK1K1XSaQEAPld45yjjwv93xF8rJkEGkgzOwjbqafwAD20eKYv1a7oj/9EC0S5jETSw==} + cspell-io@10.0.1: + resolution: {integrity: sha512-8C2ka07faxflnaqEBO3pektS21XViE/SEHT7F5ZD1ou7FyMR5u3xawTBJSczClfsxLt/WYeztBYrpmGAjmjksw==} engines: {node: '>=22.18.0'} - cspell-lib@10.0.0: - resolution: {integrity: sha512-PowW6JEjuv/F2aFEirZvBxpzHdchOnpsUJbeIcFcai0++taLTbHQObROBEBf7e0S8DnHpVD5TZkqrTME5e44wg==} + cspell-lib@10.0.1: + resolution: {integrity: sha512-RpsIPiLzc4/YMW8BMRKpyJ81x439qjYWcqgdKeXnMkbKM88J9PexzutfFf/4v97v96KzfNitEzMpbI0uj8OeUg==} engines: {node: '>=22.18.0'} - cspell-trie-lib@10.0.0: - resolution: {integrity: sha512-R8qrMx10E/bm3Lecslwxn9XYo5NzSRK1rtandEX5n9UmEYHoBXjZELkg5+TOnV8VgrVaJSK57XtcGrbKp/4kSg==} + cspell-trie-lib@10.0.1: + resolution: {integrity: sha512-BFvhalSkRQFjKrZ//FKK7fRGrZFpifnxB5AwCkzsIsBZqicsfafcQ1xP21qpb0QqyV/IomjNgviG+tRJs+0rMw==} engines: {node: '>=22.18.0'} peerDependencies: - '@cspell/cspell-types': 10.0.0 + '@cspell/cspell-types': 10.0.1 css-declaration-sorter@7.4.0: resolution: {integrity: sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==} @@ -5984,8 +5984,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-equals@6.0.0: - resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==} + fast-equals@6.0.2: + resolution: {integrity: sha512-sAjhj9ZhOxYCGiNMnZLaucOqf5ZeFnHNoKoAZiD9thhJ0N8RP85qJK759/97C/3L7NzzmGVB5uiX9AUpySZmUQ==} engines: {node: '>=6.0.0'} fast-glob@3.3.3: @@ -8460,6 +8460,10 @@ packages: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} + engines: {node: '>= 18'} + socket.io-adapter@2.5.8: resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==} @@ -11177,26 +11181,26 @@ snapshots: '@colors/colors@1.5.0': {} - '@cspell/cspell-bundled-dicts@10.0.0': + '@cspell/cspell-bundled-dicts@10.0.1': dependencies: '@cspell/dict-ada': 4.1.1 '@cspell/dict-al': 1.1.1 '@cspell/dict-aws': 4.0.17 '@cspell/dict-bash': 4.2.3 - '@cspell/dict-companies': 3.2.11 - '@cspell/dict-cpp': 7.0.2 + '@cspell/dict-companies': 3.2.12 + '@cspell/dict-cpp': 7.1.0 '@cspell/dict-cryptocurrencies': 5.0.5 '@cspell/dict-csharp': 4.0.8 '@cspell/dict-css': 4.1.2 '@cspell/dict-dart': 2.3.2 - '@cspell/dict-data-science': 2.0.14 + '@cspell/dict-data-science': 2.0.16 '@cspell/dict-django': 4.1.6 '@cspell/dict-docker': 1.1.17 '@cspell/dict-dotnet': 5.0.13 '@cspell/dict-elixir': 4.0.8 - '@cspell/dict-en-common-misspellings': 2.1.12 - '@cspell/dict-en-gb-mit': 3.1.24 - '@cspell/dict-en_us': 4.4.35 + '@cspell/dict-en-common-misspellings': 2.2.0 + '@cspell/dict-en-gb-mit': 3.1.25 + '@cspell/dict-en_us': 4.4.36 '@cspell/dict-filetypes': 3.0.18 '@cspell/dict-flutter': 1.1.1 '@cspell/dict-fonts': 4.0.6 @@ -11204,52 +11208,52 @@ snapshots: '@cspell/dict-fullstack': 3.2.9 '@cspell/dict-gaming-terms': 1.1.2 '@cspell/dict-git': 3.1.0 - '@cspell/dict-golang': 6.0.26 + '@cspell/dict-golang': 6.0.27 '@cspell/dict-google': 1.0.9 '@cspell/dict-haskell': 4.0.6 - '@cspell/dict-html': 4.0.15 + '@cspell/dict-html': 4.0.16 '@cspell/dict-html-symbol-entities': 4.0.5 '@cspell/dict-java': 5.0.12 '@cspell/dict-julia': 1.1.1 - '@cspell/dict-k8s': 1.0.12 + '@cspell/dict-k8s': 1.0.13 '@cspell/dict-kotlin': 1.1.1 '@cspell/dict-latex': 5.1.0 '@cspell/dict-lorem-ipsum': 4.0.5 '@cspell/dict-lua': 4.0.8 '@cspell/dict-makefile': 1.0.5 - '@cspell/dict-markdown': 2.0.17(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3) - '@cspell/dict-monkeyc': 1.0.12 + '@cspell/dict-markdown': 2.0.18(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.16)(@cspell/dict-typescript@3.2.3) + '@cspell/dict-monkeyc': 1.1.0 '@cspell/dict-node': 5.0.9 - '@cspell/dict-npm': 5.2.41 + '@cspell/dict-npm': 5.2.46 '@cspell/dict-php': 4.1.1 '@cspell/dict-powershell': 5.0.15 '@cspell/dict-public-licenses': 2.0.16 - '@cspell/dict-python': 4.2.27 + '@cspell/dict-python': 4.4.0 '@cspell/dict-r': 2.1.1 - '@cspell/dict-ruby': 5.1.1 + '@cspell/dict-ruby': 5.1.2 '@cspell/dict-rust': 4.1.2 '@cspell/dict-scala': 5.0.9 '@cspell/dict-shell': 1.2.0 - '@cspell/dict-software-terms': 5.2.2 + '@cspell/dict-software-terms': 5.4.1 '@cspell/dict-sql': 2.2.1 '@cspell/dict-svelte': 1.0.7 '@cspell/dict-swift': 2.0.6 - '@cspell/dict-terraform': 1.1.3 + '@cspell/dict-terraform': 1.1.4 '@cspell/dict-typescript': 3.2.3 '@cspell/dict-vue': 3.0.5 '@cspell/dict-zig': 1.0.0 - '@cspell/cspell-performance-monitor@10.0.0': {} + '@cspell/cspell-performance-monitor@10.0.1': {} - '@cspell/cspell-pipe@10.0.0': {} + '@cspell/cspell-pipe@10.0.1': {} - '@cspell/cspell-resolver@10.0.0': + '@cspell/cspell-resolver@10.0.1': dependencies: global-directory: 5.0.0 - '@cspell/cspell-service-bus@10.0.0': {} + '@cspell/cspell-service-bus@10.0.1': {} - '@cspell/cspell-types@10.0.0': {} + '@cspell/cspell-types@10.0.1': {} '@cspell/dict-ada@4.1.1': {} @@ -11261,9 +11265,9 @@ snapshots: dependencies: '@cspell/dict-shell': 1.2.0 - '@cspell/dict-companies@3.2.11': {} + '@cspell/dict-companies@3.2.12': {} - '@cspell/dict-cpp@7.0.2': {} + '@cspell/dict-cpp@7.1.0': {} '@cspell/dict-cryptocurrencies@5.0.5': {} @@ -11273,7 +11277,7 @@ snapshots: '@cspell/dict-dart@2.3.2': {} - '@cspell/dict-data-science@2.0.14': {} + '@cspell/dict-data-science@2.0.16': {} '@cspell/dict-django@4.1.6': {} @@ -11283,11 +11287,11 @@ snapshots: '@cspell/dict-elixir@4.0.8': {} - '@cspell/dict-en-common-misspellings@2.1.12': {} + '@cspell/dict-en-common-misspellings@2.2.0': {} - '@cspell/dict-en-gb-mit@3.1.24': {} + '@cspell/dict-en-gb-mit@3.1.25': {} - '@cspell/dict-en_us@4.4.35': {} + '@cspell/dict-en_us@4.4.36': {} '@cspell/dict-filetypes@3.0.18': {} @@ -11303,7 +11307,7 @@ snapshots: '@cspell/dict-git@3.1.0': {} - '@cspell/dict-golang@6.0.26': {} + '@cspell/dict-golang@6.0.27': {} '@cspell/dict-google@1.0.9': {} @@ -11311,13 +11315,13 @@ snapshots: '@cspell/dict-html-symbol-entities@4.0.5': {} - '@cspell/dict-html@4.0.15': {} + '@cspell/dict-html@4.0.16': {} '@cspell/dict-java@5.0.12': {} '@cspell/dict-julia@1.1.1': {} - '@cspell/dict-k8s@1.0.12': {} + '@cspell/dict-k8s@1.0.13': {} '@cspell/dict-kotlin@1.1.1': {} @@ -11329,18 +11333,18 @@ snapshots: '@cspell/dict-makefile@1.0.5': {} - '@cspell/dict-markdown@2.0.17(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.15)(@cspell/dict-typescript@3.2.3)': + '@cspell/dict-markdown@2.0.18(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.16)(@cspell/dict-typescript@3.2.3)': dependencies: '@cspell/dict-css': 4.1.2 - '@cspell/dict-html': 4.0.15 + '@cspell/dict-html': 4.0.16 '@cspell/dict-html-symbol-entities': 4.0.5 '@cspell/dict-typescript': 3.2.3 - '@cspell/dict-monkeyc@1.0.12': {} + '@cspell/dict-monkeyc@1.1.0': {} '@cspell/dict-node@5.0.9': {} - '@cspell/dict-npm@5.2.41': {} + '@cspell/dict-npm@5.2.46': {} '@cspell/dict-php@4.1.1': {} @@ -11348,13 +11352,13 @@ snapshots: '@cspell/dict-public-licenses@2.0.16': {} - '@cspell/dict-python@4.2.27': + '@cspell/dict-python@4.4.0': dependencies: - '@cspell/dict-data-science': 2.0.14 + '@cspell/dict-data-science': 2.0.16 '@cspell/dict-r@2.1.1': {} - '@cspell/dict-ruby@5.1.1': {} + '@cspell/dict-ruby@5.1.2': {} '@cspell/dict-rust@4.1.2': {} @@ -11362,7 +11366,7 @@ snapshots: '@cspell/dict-shell@1.2.0': {} - '@cspell/dict-software-terms@5.2.2': {} + '@cspell/dict-software-terms@5.4.1': {} '@cspell/dict-sql@2.2.1': {} @@ -11370,7 +11374,7 @@ snapshots: '@cspell/dict-swift@2.0.6': {} - '@cspell/dict-terraform@1.1.3': {} + '@cspell/dict-terraform@1.1.4': {} '@cspell/dict-typescript@3.2.3': {} @@ -11378,26 +11382,26 @@ snapshots: '@cspell/dict-zig@1.0.0': {} - '@cspell/dynamic-import@10.0.0': + '@cspell/dynamic-import@10.0.1': dependencies: - '@cspell/url': 10.0.0 + '@cspell/url': 10.0.1 import-meta-resolve: 4.2.0 - '@cspell/eslint-plugin@10.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))': + '@cspell/eslint-plugin@10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))': dependencies: - '@cspell/cspell-types': 10.0.0 - '@cspell/url': 10.0.0 - cspell-lib: 10.0.0 + '@cspell/cspell-types': 10.0.1 + '@cspell/url': 10.0.1 + cspell-lib: 10.0.1 eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) synckit: 0.11.13 - '@cspell/filetypes@10.0.0': {} + '@cspell/filetypes@10.0.1': {} - '@cspell/rpc@10.0.0': {} + '@cspell/rpc@10.0.1': {} - '@cspell/strong-weak-map@10.0.0': {} + '@cspell/strong-weak-map@10.0.1': {} - '@cspell/url@10.0.0': {} + '@cspell/url@10.0.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -15030,7 +15034,7 @@ snapshots: commander@8.3.0: {} - comment-json@4.6.2: + comment-json@5.0.0: dependencies: array-timsort: 1.0.3 esprima: 4.0.1 @@ -15173,54 +15177,54 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cspell-config-lib@10.0.0: + cspell-config-lib@10.0.1: dependencies: - '@cspell/cspell-types': 10.0.0 - comment-json: 4.6.2 - smol-toml: 1.6.1 + '@cspell/cspell-types': 10.0.1 + comment-json: 5.0.0 + smol-toml: 1.8.0 yaml: 2.9.0 - cspell-dictionary@10.0.0: + cspell-dictionary@10.0.1: dependencies: - '@cspell/cspell-performance-monitor': 10.0.0 - '@cspell/cspell-pipe': 10.0.0 - '@cspell/cspell-types': 10.0.0 - cspell-trie-lib: 10.0.0(@cspell/cspell-types@10.0.0) - fast-equals: 6.0.0 + '@cspell/cspell-performance-monitor': 10.0.1 + '@cspell/cspell-pipe': 10.0.1 + '@cspell/cspell-types': 10.0.1 + cspell-trie-lib: 10.0.1(@cspell/cspell-types@10.0.1) + fast-equals: 6.0.2 - cspell-glob@10.0.0: + cspell-glob@10.0.1: dependencies: - '@cspell/url': 10.0.0 + '@cspell/url': 10.0.1 picomatch: 4.0.5 - cspell-grammar@10.0.0: + cspell-grammar@10.0.1: dependencies: - '@cspell/cspell-pipe': 10.0.0 - '@cspell/cspell-types': 10.0.0 + '@cspell/cspell-pipe': 10.0.1 + '@cspell/cspell-types': 10.0.1 - cspell-io@10.0.0: + cspell-io@10.0.1: dependencies: - '@cspell/cspell-service-bus': 10.0.0 - '@cspell/url': 10.0.0 + '@cspell/cspell-service-bus': 10.0.1 + '@cspell/url': 10.0.1 - cspell-lib@10.0.0: + cspell-lib@10.0.1: dependencies: - '@cspell/cspell-bundled-dicts': 10.0.0 - '@cspell/cspell-performance-monitor': 10.0.0 - '@cspell/cspell-pipe': 10.0.0 - '@cspell/cspell-resolver': 10.0.0 - '@cspell/cspell-types': 10.0.0 - '@cspell/dynamic-import': 10.0.0 - '@cspell/filetypes': 10.0.0 - '@cspell/rpc': 10.0.0 - '@cspell/strong-weak-map': 10.0.0 - '@cspell/url': 10.0.0 - cspell-config-lib: 10.0.0 - cspell-dictionary: 10.0.0 - cspell-glob: 10.0.0 - cspell-grammar: 10.0.0 - cspell-io: 10.0.0 - cspell-trie-lib: 10.0.0(@cspell/cspell-types@10.0.0) + '@cspell/cspell-bundled-dicts': 10.0.1 + '@cspell/cspell-performance-monitor': 10.0.1 + '@cspell/cspell-pipe': 10.0.1 + '@cspell/cspell-resolver': 10.0.1 + '@cspell/cspell-types': 10.0.1 + '@cspell/dynamic-import': 10.0.1 + '@cspell/filetypes': 10.0.1 + '@cspell/rpc': 10.0.1 + '@cspell/strong-weak-map': 10.0.1 + '@cspell/url': 10.0.1 + cspell-config-lib: 10.0.1 + cspell-dictionary: 10.0.1 + cspell-glob: 10.0.1 + cspell-grammar: 10.0.1 + cspell-io: 10.0.1 + cspell-trie-lib: 10.0.1(@cspell/cspell-types@10.0.1) env-paths: 4.0.0 gensequence: 8.0.8 import-fresh: 4.0.0 @@ -15229,9 +15233,9 @@ snapshots: vscode-uri: 3.1.0 xdg-basedir: 5.1.0 - cspell-trie-lib@10.0.0(@cspell/cspell-types@10.0.0): + cspell-trie-lib@10.0.1(@cspell/cspell-types@10.0.1): dependencies: - '@cspell/cspell-types': 10.0.0 + '@cspell/cspell-types': 10.0.1 css-declaration-sorter@7.4.0(postcss@8.5.25): dependencies: @@ -16071,7 +16075,7 @@ snapshots: fast-diff@1.3.0: {} - fast-equals@6.0.0: {} + fast-equals@6.0.2: {} fast-glob@3.3.3: dependencies: @@ -18862,6 +18866,8 @@ snapshots: smol-toml@1.6.1: {} + smol-toml@1.8.0: {} + socket.io-adapter@2.5.8(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) From f9940f0db898c779f9d6673847ffd744a2c5a666 Mon Sep 17 00:00:00 2001 From: Alex Molodyh <140214274+amolodyh-hyland@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:19:19 -0700 Subject: [PATCH 22/31] AAE-46057 Refine form field status spacing (#12196) --- .../form/components/form-renderer.component.scss | 8 ++++---- .../components/widgets/amount/amount.widget.html | 9 +++++++-- .../widgets/date-time/date-time.widget.html | 4 +++- .../form/components/widgets/date/date.widget.html | 5 +++-- .../widgets/decimal/decimal.component.html | 5 +++-- .../widgets/hyperlink/hyperlink.widget.scss | 3 +-- .../multiline-text/multiline-text.widget.html | 7 +++++-- .../components/widgets/number/number.widget.html | 5 +++-- .../form/components/widgets/text/text.widget.html | 11 +++++++++-- lib/core/src/lib/styles/_mat-selectors.scss | 1 - .../widgets/data-table/data-table.widget.scss | 13 +++++++------ .../components/widgets/date/date-cloud.widget.html | 9 +++++++-- .../display-external-property.widget.html | 5 +++-- .../widgets/dropdown/dropdown-cloud.widget.html | 5 +++-- .../radio-buttons/radio-buttons-cloud.widget.scss | 4 ---- .../lib/group/components/group-cloud.component.scss | 1 + .../people/components/people-cloud.component.scss | 4 ++++ .../lib/form/widgets/dropdown/dropdown.widget.html | 5 +++-- .../form/widgets/typeahead/typeahead.widget.html | 5 +++-- 19 files changed, 69 insertions(+), 40 deletions(-) diff --git a/lib/core/src/lib/form/components/form-renderer.component.scss b/lib/core/src/lib/form/components/form-renderer.component.scss index 956c09af4d..7f88f2a7f5 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.scss +++ b/lib/core/src/lib/form/components/form-renderer.component.scss @@ -21,10 +21,10 @@ width: auto; } - .adf-form-field-input:not(.adf-inplace-input-mat-form-field, .adf-people-cloud, .adf-cloud-group) { - #{ms.$mat-form-field-subscript-wrapper} { - height: 40px; - } + .adf-form-field-status-slot { + display: block; + height: 40px; + box-sizing: border-box; } } diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html index 00fefd8e34..76104bba6d 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html @@ -10,7 +10,11 @@ >
- + @if ( (field.name || field?.required) && !field.leftLabels) { {{field.name | translate }} } @if(!enableDisplayBasedOnLocale) { {{ currency }}  @@ -32,12 +36,13 @@ (blur)="amountWidgetOnBlur()" /> @if (field.validationSummary?.message || (isInvalidFieldRequired() && isTouched())) { - + error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate:translateParameters }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } +
diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html index d1c5a7b33b..1457221133 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html @@ -10,6 +10,7 @@
@if( (field.name || field?.required) && !field.leftLabels) { @@ -38,11 +39,12 @@ [timeInterval]="5" [disabled]="field.readOnly" /> @if (datetimeInputControl.invalid && datetimeInputControl.touched && field.validationSummary?.message) { - + error_outline {{ field.validationSummary.message | translate:translateParameters }} } +
diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.html b/lib/core/src/lib/form/components/widgets/date/date.widget.html index f36c6ae24e..8633b8db24 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.html +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.html @@ -1,7 +1,7 @@
- + @@ -22,11 +22,12 @@ [startAt]="startAt" [disabled]="field.readOnly" /> @if (dateInputControl.invalid && dateInputControl.touched) { - + error_outline @if (dateInputControl.hasError('required')) {{{ 'FORM.FIELD.REQUIRED' | translate }}} @else if (dateInputControl.hasError('matDatepickerParse')) {{{ 'FORM.FIELD.VALIDATOR.INVALID_DATE_FORMAT' | translate: { format: field.dateDisplayFormat || field.defaultDateTimeFormat } }}} @else if (dateInputControl.hasError('matDatepickerMin')) {{{ 'FORM.FIELD.VALIDATOR.NOT_LESS_THAN' | translate: { minValue: formattedMinDate } }}} @else if (dateInputControl.hasError('matDatepickerMax')) {{{ 'FORM.FIELD.VALIDATOR.NOT_GREATER_THAN' | translate: { maxValue: formattedMaxDate } }}} } +
diff --git a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.html b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.html index e2c5be4965..c531e78288 100644 --- a/lib/core/src/lib/form/components/widgets/decimal/decimal.component.html +++ b/lib/core/src/lib/form/components/widgets/decimal/decimal.component.html @@ -9,7 +9,7 @@
- + @if ( (field.name || field?.required) && !field.leftLabels) { {{ field.name | translate }} } @if (field.validationSummary?.message || (isInvalidFieldRequired() && isTouched())) { - + error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate:translateParameters }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } +
diff --git a/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss b/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss index 91025303f0..4dedad8795 100644 --- a/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss +++ b/lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.scss @@ -1,7 +1,6 @@ .adf-hyperlink-widget { padding: 0.4375em 0; - border-top: 0.8438em solid transparent; - margin-bottom: 20px; + margin-bottom: 40px; a { color: var(--mat-sys-primary); diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html index cea1673327..73d7d5036a 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html @@ -7,6 +7,7 @@ @@ -31,14 +32,16 @@ > @if (field.validationSummary?.message || (isInvalidFieldRequired() && isTouched())) { - + @if (field.maxLength > 0) {{{ field?.value?.length || 0 }}/{{ field.maxLength }}} error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate:translateParameters }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } @else if (field.maxLength > 0) { - {{ field?.value?.length || 0 }}/{{ field.maxLength }} + {{ field?.value?.length || 0 }}/{{ field.maxLength }} + } @else { + diff --git a/lib/core/src/lib/form/components/widgets/number/number.widget.html b/lib/core/src/lib/form/components/widgets/number/number.widget.html index 11999ea2b1..344d5c9abd 100644 --- a/lib/core/src/lib/form/components/widgets/number/number.widget.html +++ b/lib/core/src/lib/form/components/widgets/number/number.widget.html @@ -9,7 +9,7 @@
- + @if( (field.name || this.field?.required) && !field.leftLabels) { {{ field.name | translate }} @@ -30,12 +30,13 @@ [errorStateMatcher]="errorStateMatcher" (blur)="onBlur()"> @if (field.validationSummary?.message || (isInvalidFieldRequired() && isTouched())) { - + error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate:translateParameters }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } +
diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.html b/lib/core/src/lib/form/components/widgets/text/text.widget.html index e1fc6d47e0..e9dee6e3b9 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.html +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.html @@ -8,7 +8,11 @@
- + @if ( (field.name || this.field?.required) && !field.leftLabels) { {{ field.name | translate }} @@ -30,7 +34,7 @@ (paste)="onPaste($event)" (blur)="onBlur()"> @if (!fieldStatusTemplate && (maxLengthPasteError.isActive() || field.validationSummary?.message || (isInvalidFieldRequired() && isTouched()))) { - + @if (maxLengthPasteError.isActive()) { @@ -43,6 +47,9 @@ } + @if (!fieldStatusTemplate) { + diff --git a/lib/core/src/lib/styles/_mat-selectors.scss b/lib/core/src/lib/styles/_mat-selectors.scss index dba941cafa..31fa2b1208 100644 --- a/lib/core/src/lib/styles/_mat-selectors.scss +++ b/lib/core/src/lib/styles/_mat-selectors.scss @@ -17,7 +17,6 @@ $mat-button: '.mat-mdc-button'; $mat-button-label: '.mdc-button__label'; $mat-form-field: '.mat-mdc-form-field'; $mat-form-field-wrapper: '.mat-mdc-text-field-wrapper'; -$mat-form-field-subscript-wrapper: '.mat-mdc-form-field-subscript-wrapper'; $mat-line-ripple: '.mdc-line-ripple'; $mat-form-field-prefix: '.mat-mdc-form-field-text-prefix'; $mat-form-field-suffix: '.mat-mdc-form-field-text-suffix'; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss index ed9398c207..8fcb2550ac 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss +++ b/lib/process-services-cloud/src/lib/form/components/widgets/data-table/data-table.widget.scss @@ -1,11 +1,12 @@ .adf-data-table-widget-failed-message { display: block; - margin: 10px; } -.adf-preview-placeholder { - height: 100%; - width: 100%; - min-height: 100px; - margin-bottom: 10px; +.adf-data-table-widget-container { + .adf-preview-placeholder { + height: 100%; + width: 100%; + min-height: 100px; + margin-bottom: 10px; + } } diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html index 1cc7d8e98b..bde9f411f9 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html @@ -15,7 +15,11 @@ >
- + @if ( (field.name || field?.required) && !field.leftLabels) { {{field.name | translate }} ({{field.dateDisplayFormat}}) @@ -36,12 +40,13 @@ @if (dateInputControl.invalid && dateInputControl.touched) { - + error_outline @if (dateInputControl.hasError('required')) {{{ 'FORM.FIELD.REQUIRED' | translate }}} @else if (dateInputControl.hasError('matDatepickerParse')) {{{ 'FORM.FIELD.VALIDATOR.INVALID_DATE_FORMAT' | translate: { format: field.dateDisplayFormat || field.defaultDateTimeFormat } }}} @else if (dateInputControl.hasError('matDatepickerMin')) {{{ 'FORM.FIELD.VALIDATOR.NOT_LESS_THAN' | translate: { minValue: formattedMinDate } }}} @else if (dateInputControl.hasError('matDatepickerMax')) {{{ 'FORM.FIELD.VALIDATOR.NOT_GREATER_THAN' | translate: { maxValue: formattedMaxDate } }}} } +
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.html index 39a356ac8d..709d994c44 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.html @@ -11,7 +11,7 @@
- + @if( (field.name || field?.required) && !field.leftLabels) { {{ field.name | translate }} } @@ -32,8 +32,9 @@ @if (propertyLoadFailed && !previewState) { - error_outline{{ 'FORM.FIELD.EXTERNAL_PROPERTY_LOAD_FAILED' | translate }} + error_outline{{ 'FORM.FIELD.EXTERNAL_PROPERTY_LOAD_FAILED' | translate }} } +
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html index eedec326ff..e994d83967 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html @@ -12,7 +12,7 @@ }
- + @if ( (field.name || this.field?.required) && !field.leftLabels) { {{ field.name | translate }} } @@ -49,12 +49,13 @@ } @if ((dropdownControl.hasError('required') && !isRestApiFailed && !variableOptionsFailed) || (!previewState && !field.readOnly && (isRestApiFailed || variableOptionsFailed))) { - + error_outline @if (dropdownControl.hasError('required') && !isRestApiFailed && !variableOptionsFailed) {{{ 'FORM.FIELD.REQUIRED' | translate }}} @else if (isRestApiFailed) {{{ 'FORM.FIELD.REST_API_FAILED' | translate: { hostname: restApiHostName } }}} @else if (variableOptionsFailed) {{{ 'FORM.FIELD.VARIABLE_DROPDOWN_OPTIONS_FAILED' | translate }}} } +
diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss index ae2d2c54f7..ca6a8fae94 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss +++ b/lib/process-services-cloud/src/lib/form/components/widgets/radio-buttons/radio-buttons-cloud.widget.scss @@ -45,8 +45,4 @@ word-break: break-word; } } - - &-radio-group-error-message .adf-error-container { - margin-top: 5px; - } } diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss index 7cca3876c9..d7e1aa5a21 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.scss @@ -63,6 +63,7 @@ } .adf-error { + padding-top: 3px; animation: slide-down-fade-in 300ms cubic-bezier(0.55, 0, 0.55, 0.2); } } diff --git a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.scss b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.scss index 668683bfc0..a4ed4e3cdb 100644 --- a/lib/process-services-cloud/src/lib/people/components/people-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/people/components/people-cloud.component.scss @@ -66,6 +66,10 @@ @include mixins.adf-error-icon; } + .adf-error { + padding-top: 3px; + } + .adf-error-animate { animation: adf-people-cloud-slide-in-down 300ms cubic-bezier(0.55, 0, 0.55, 0.2); } diff --git a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.html b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.html index 55a0f27e0c..744843e374 100644 --- a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.html +++ b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.html @@ -6,17 +6,18 @@ - + {{opt.name}} {{field.value}} @if (!isReadOnlyField && dropdownControl.touched && (field.validationSummary?.message || dropdownControl.hasError('required'))) { - + error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } + diff --git a/lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.html b/lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.html index c4160a0040..8cea85eaa1 100644 --- a/lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.html +++ b/lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.html @@ -4,7 +4,7 @@ [class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" id="typehead-div"> - +
@if (field.validationSummary?.message || isInvalidFieldRequired()) { - + error_outline @if (field.validationSummary?.message) {{{ field.validationSummary.message | translate }}} @else {{{ 'FORM.FIELD.REQUIRED' | translate }}} } +
From f2f3edb6c333cd25e127970200d289ba0d317354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:14:07 +0100 Subject: [PATCH 23/31] build(deps): bump actions/download-artifact from 4.3.0 to 8.0.1 (#12195) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.3.0...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/unit-test-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test-workflow.yml b/.github/workflows/unit-test-workflow.yml index 2b52941aec..c86494dca7 100644 --- a/.github/workflows/unit-test-workflow.yml +++ b/.github/workflows/unit-test-workflow.yml @@ -117,7 +117,7 @@ jobs: with: fetch-depth: 0 - name: Download all coverage artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: coverage-* path: coverage-reports From ce15f8f4101ee4597998d3da38db452fc0b0c27c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:14:30 +0100 Subject: [PATCH 24/31] build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 (#12194) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.6.2...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/unit-test-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test-workflow.yml b/.github/workflows/unit-test-workflow.yml index c86494dca7..5f79dd3aa3 100644 --- a/.github/workflows/unit-test-workflow.yml +++ b/.github/workflows/unit-test-workflow.yml @@ -91,7 +91,7 @@ jobs: xvfb-run --auto-servernum pnpm nx run ${{ matrix.project }}:test - name: Upload coverage report if: ${{ always() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage-${{ matrix.project }} path: coverage/${{ matrix.project }}/lcov.info From 6816205f7568d00f378f79be69ce240bbe2bc8d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:17:45 +0100 Subject: [PATCH 25/31] build(deps): bump SonarSource/sonarqube-scan-action from 5.1.0 to 8.2.1 (#12193) Bumps [SonarSource/sonarqube-scan-action](https://github.com/sonarsource/sonarqube-scan-action) from 5.1.0 to 8.2.1. - [Release notes](https://github.com/sonarsource/sonarqube-scan-action/releases) - [Commits](https://github.com/sonarsource/sonarqube-scan-action/compare/aa494459d7c39c106cc77b166de8b4250a32bb97...22918119ff8e1ca75a623e15c8296b6ea4fbe28f) --- updated-dependencies: - dependency-name: SonarSource/sonarqube-scan-action dependency-version: 8.2.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/unit-test-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test-workflow.yml b/.github/workflows/unit-test-workflow.yml index 5f79dd3aa3..9de5b882cd 100644 --- a/.github/workflows/unit-test-workflow.yml +++ b/.github/workflows/unit-test-workflow.yml @@ -138,7 +138,7 @@ jobs: echo "Coverage files found:" find coverage -name 'lcov.info' -type f - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0 + uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: https://sonarcloud.io From 3f2c81de860cbcdf5da43029191a46d49b480b2f Mon Sep 17 00:00:00 2001 From: Tomasz Gnyp <49343696+tomgny@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:12:40 +0200 Subject: [PATCH 26/31] AAE-50899 Improve form renderer for loop track (#12198) --- .../src/lib/form/components/form-renderer.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/src/lib/form/components/form-renderer.component.html b/lib/core/src/lib/form/components/form-renderer.component.html index 337ad73c58..d025b0e7e1 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.html +++ b/lib/core/src/lib/form/components/form-renderer.component.html @@ -98,7 +98,7 @@ [hidden]="!currentRootElement?.isVisible" > - @for (row of currentRootElement.field.rows; track row; let rowIndex = $index) { + @for (row of currentRootElement.field.rows; track row.id; let rowIndex = $index) { @let hasMultipleRows = currentRootElement.field.rows.length > 1;
- @for (column of row.columns; track column; let columnIndex = $index) { + @for (column of row.columns; track column.id; let columnIndex = $index) {
- @for (field of column?.fields; track field) { + @for (field of column?.fields; track field.id) { @if (field.type === 'section') { } @else { From 1e40e5effd96c0ea77e886a792a67ca3c7fb955a Mon Sep 17 00:00:00 2001 From: Maurizio Vitale Date: Thu, 27 Aug 2026 12:47:49 +0100 Subject: [PATCH 27/31] fix(core): fix ResizableDirective memory leak from orphaned document listeners [AAE-50892] (#12197) * fix(core): fix ResizableDirective memory leak from orphaned document listeners [AAE-50892] Return unlisten teardown from Observable subscribers so RxJS properly removes document event listeners when share() refcount drops to zero. Previously, renderer.listen() unlisten functions were stored in mutable fields that got overwritten on each drag cycle, orphaning old listeners and retaining entire detached DOM subtrees via the closure chain. * fix after second report --- .../resizable/resizable.directive.spec.ts | 67 ++++++++++++++----- .../resizable/resizable.directive.ts | 35 ++-------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts index 3317dfeeb2..0e0b19140b 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts @@ -16,7 +16,7 @@ */ import { TestBed } from '@angular/core/testing'; -import { ElementRef, Injector, NgZone, Renderer2, runInInjectionContext } from '@angular/core'; +import { ElementRef, EnvironmentInjector, NgZone, Renderer2, createEnvironmentInjector, runInInjectionContext } from '@angular/core'; import { ResizableDirective } from './resizable.directive'; describe('ResizableDirective', () => { @@ -24,6 +24,7 @@ describe('ResizableDirective', () => { let renderer: Renderer2; let element: ElementRef; let directive: ResizableDirective; + let testEnvInjector: EnvironmentInjector; const scrollTop = 0; const scrollLeft = 0; @@ -39,8 +40,14 @@ describe('ResizableDirective', () => { scrollLeft }; + let unlistenSpies: jasmine.Spy[]; + const rendererMock = { - listen: jasmine.createSpy('listen'), + listen: jasmine.createSpy('listen').and.callFake(() => { + const spy = jasmine.createSpy(`unlisten-${unlistenSpies.length}`); + unlistenSpies.push(spy); + return spy; + }), setStyle: jasmine.createSpy('setStyle') }; @@ -53,6 +60,10 @@ describe('ResizableDirective', () => { }; beforeEach(() => { + unlistenSpies = []; + rendererMock.listen.calls.reset(); + rendererMock.setStyle.calls.reset(); + TestBed.configureTestingModule({ imports: [ResizableDirective], providers: [ @@ -64,29 +75,28 @@ describe('ResizableDirective', () => { element = TestBed.inject(ElementRef); renderer = TestBed.inject(Renderer2); ngZone = TestBed.inject(NgZone); - const injector = TestBed.inject(Injector); spyOn(ngZone, 'runOutsideAngular').and.callFake((fn) => fn()); spyOn(ngZone, 'run').and.callFake((fn) => fn()); - const testInjector = Injector.create({ - providers: [ + testEnvInjector = createEnvironmentInjector( + [ { provide: Renderer2, useValue: renderer }, { provide: ElementRef, useValue: element }, { provide: NgZone, useValue: ngZone } ], - parent: injector - }); + TestBed.inject(EnvironmentInjector) + ); - directive = runInInjectionContext(testInjector, () => new ResizableDirective()); + directive = runInInjectionContext(testEnvInjector, () => new ResizableDirective()); directive.ngOnInit(); }); - it('should attach mousedown event to document', () => { - expect(renderer.listen).toHaveBeenCalledWith('document', 'mousedown', jasmine.any(Function)); + it('should not attach any document listeners on init', () => { + expect(renderer.listen).not.toHaveBeenCalled(); }); - it('should attach mousemove event to document', () => { + it('should attach document mousemove listener only during active drag', () => { const mouseDownEvent = new MouseEvent('mousedown'); directive.mousedown.next({ ...mouseDownEvent, resize: true }); @@ -94,10 +104,6 @@ describe('ResizableDirective', () => { expect(renderer.listen).toHaveBeenCalledWith('document', 'mousemove', jasmine.any(Function)); }); - it('should attach mouseup event to document', () => { - expect(renderer.listen).toHaveBeenCalledWith('document', 'mouseup', jasmine.any(Function)); - }); - it('should should set the cursor on mouse down', () => { spyOn(directive.resizeStart, 'emit'); const mouseDownEvent = new MouseEvent('mousedown'); @@ -174,4 +180,35 @@ describe('ResizableDirective', () => { expect(directive.keyboardResizing.emit).toHaveBeenCalledWith({ rectangle: { top: 0, left: 0, bottom: 0, right: step, width: step } }); }); + + it('should unregister document listeners on destroy', () => { + directive.mousedown.next({ ...new MouseEvent('mousedown'), resize: true }); + expect(unlistenSpies.length).toBeGreaterThan(0); + + testEnvInjector.destroy(); + unlistenSpies.forEach((spy) => expect(spy).toHaveBeenCalledTimes(1)); + }); + + it('should not accumulate mousemove listeners across repeated drag cycles', () => { + const listenCountAfterInit = rendererMock.listen.calls.count(); + + const mouseDownEvent = new MouseEvent('mousedown'); + const mouseUpEvent = new MouseEvent('mouseup'); + + directive.mousedown.next({ ...mouseDownEvent, resize: true }); + const listenCountAfterFirstDrag = rendererMock.listen.calls.count(); + expect(listenCountAfterFirstDrag).toBeGreaterThan(listenCountAfterInit); + + directive.mouseup.next(mouseUpEvent); + const unlistenedAfterFirstDrag = unlistenSpies.filter((spy) => spy.calls.count() > 0).length; + + directive.mousedown.next({ ...mouseDownEvent, resize: true }); + directive.mouseup.next(mouseUpEvent); + const unlistenedAfterSecondDrag = unlistenSpies.filter((spy) => spy.calls.count() > 0).length; + + expect(unlistenedAfterSecondDrag).toBeGreaterThan(unlistenedAfterFirstDrag); + + testEnvInjector.destroy(); + unlistenSpies.forEach((spy) => expect(spy).toHaveBeenCalledTimes(1)); + }); }); diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts index 06d168a006..9b7436759c 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts @@ -61,53 +61,35 @@ export class ResizableDirective implements OnInit, OnDestroy { mousemove = new Subject(); - private readonly pointerDown: Observable; private readonly pointerMove: Observable; - private readonly pointerUp: Observable; private startingRect: BoundingRectangle; private currentRect: BoundingRectangle; - private unsubscribeMouseDown?: () => void; - private unsubscribeMouseMove?: () => void; - private unsubscribeMouseUp?: () => void; - private readonly destroyRef = inject(DestroyRef); constructor() { const renderer = this.renderer; const zone = this.zone; - this.pointerDown = new Observable((observer: Observer) => { - zone.runOutsideAngular(() => { - this.unsubscribeMouseDown = renderer.listen('document', 'mousedown', (event: MouseEvent) => { - observer.next(event); - }); - }); - }).pipe(share()); - + // Document-level mousemove is needed for smooth drag tracking when cursor leaves the handle element. + // Only subscribed during active drag via share() refcount. this.pointerMove = new Observable((observer: Observer) => { + let stopListening: () => void = () => {}; zone.runOutsideAngular(() => { - this.unsubscribeMouseMove = renderer.listen('document', 'mousemove', (event: MouseEvent) => { - observer.next(event); - }); - }); - }).pipe(share()); - - this.pointerUp = new Observable((observer: Observer) => { - zone.runOutsideAngular(() => { - this.unsubscribeMouseUp = renderer.listen('document', 'mouseup', (event: MouseEvent) => { + stopListening = renderer.listen('document', 'mousemove', (event: MouseEvent) => { observer.next(event); }); }); + return stopListening; }).pipe(share()); } ngOnInit(): void { - const mousedown$ = merge(this.pointerDown, this.mousedown); + const mousedown$ = this.mousedown.asObservable(); const mousemove$ = merge(this.pointerMove, this.mousemove); - const mouseup$ = merge(this.pointerUp, this.mouseup); + const mouseup$ = this.mouseup.asObservable(); const mouseDrag: Observable = mousedown$ .pipe( @@ -184,9 +166,6 @@ export class ResizableDirective implements OnInit, OnDestroy { this.mousedown.complete(); this.mousemove.complete(); this.mouseup.complete(); - this.unsubscribeMouseDown?.(); - this.unsubscribeMouseMove?.(); - this.unsubscribeMouseUp?.(); } resizeByKeyboard(delta: number): void { From 91b5164ab27c0de4f0ae954572a09cbaaf50aaaa Mon Sep 17 00:00:00 2001 From: Maurizio Vitale Date: Thu, 27 Aug 2026 12:56:51 +0100 Subject: [PATCH 28/31] fix(core): fix DropZoneDirective listener leak from bind(this) mismatch [AAE-50892] (#12200) addEventListener was called with this.onDragEnter.bind(this), creating a new function reference each time, while removeEventListener was called with the unbound this.onDragEnter. The references never matched, so drag listeners were never removed on destroy. Each surviving listener closed over the directive and its host cell, retaining the whole detached datatable/task-list subtree via the parentNode chain. Heap analysis showed 624 dragenter + 624 dragover + 624 drop listeners bound to detached cells. Store the bound handlers once and use the same references for both add and remove so listeners are properly cleaned up. --- .../directives/drop-zone.directive.spec.ts | 76 +++++++++++++++++++ .../directives/drop-zone.directive.ts | 16 ++-- 2 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 lib/core/src/lib/datatable/directives/drop-zone.directive.spec.ts diff --git a/lib/core/src/lib/datatable/directives/drop-zone.directive.spec.ts b/lib/core/src/lib/datatable/directives/drop-zone.directive.spec.ts new file mode 100644 index 0000000000..66f3b58834 --- /dev/null +++ b/lib/core/src/lib/datatable/directives/drop-zone.directive.spec.ts @@ -0,0 +1,76 @@ +/*! + * @license + * Copyright © 2005-2026 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 { TestBed } from '@angular/core/testing'; +import { ElementRef } from '@angular/core'; +import { DropZoneDirective } from './drop-zone.directive'; + +describe('DropZoneDirective', () => { + let directive: DropZoneDirective; + let element: HTMLElement; + + beforeEach(() => { + element = document.createElement('div'); + + TestBed.configureTestingModule({ + providers: [{ provide: ElementRef, useValue: new ElementRef(element) }] + }); + + directive = TestBed.runInInjectionContext(() => new DropZoneDirective()); + directive.dropTarget = 'cell'; + directive.ngOnInit(); + }); + + it('should dispatch a namespaced custom event on dragenter while attached', () => { + const dispatched: string[] = []; + element.addEventListener('cell-dragenter', () => dispatched.push('cell-dragenter')); + + element.dispatchEvent(new DragEvent('dragenter')); + + expect(dispatched).toContain('cell-dragenter'); + }); + + it('should not handle drag events after the directive is destroyed', () => { + const dispatched: string[] = []; + element.addEventListener('cell-dragenter', () => dispatched.push('cell-dragenter')); + element.addEventListener('cell-dragover', () => dispatched.push('cell-dragover')); + element.addEventListener('cell-drop', () => dispatched.push('cell-drop')); + + directive.ngOnDestroy(); + + element.dispatchEvent(new DragEvent('dragenter')); + element.dispatchEvent(new DragEvent('dragover')); + element.dispatchEvent(new DragEvent('drop')); + + expect(dispatched).toEqual([]); + }); + + it('should remove listeners using the same references that were added', () => { + const addSpy = spyOn(element, 'addEventListener').and.callThrough(); + const removeSpy = spyOn(element, 'removeEventListener').and.callThrough(); + + directive.ngOnInit(); + directive.ngOnDestroy(); + + const addedByEvent = new Map(); + addSpy.calls.allArgs().forEach(([evt, fn]) => addedByEvent.set(evt as string, fn as EventListenerOrEventListenerObject)); + + removeSpy.calls.allArgs().forEach(([evt, fn]) => { + expect(fn).toBe(addedByEvent.get(evt as string)); + }); + }); +}); diff --git a/lib/core/src/lib/datatable/directives/drop-zone.directive.ts b/lib/core/src/lib/datatable/directives/drop-zone.directive.ts index 4d629db4ff..4da7c61e56 100644 --- a/lib/core/src/lib/datatable/directives/drop-zone.directive.ts +++ b/lib/core/src/lib/datatable/directives/drop-zone.directive.ts @@ -36,6 +36,10 @@ export class DropZoneDirective implements OnInit, OnDestroy { @Input() dropColumn: DataColumn; + private readonly onDragEnterHandler = this.onDragEnter.bind(this); + private readonly onDragOverHandler = this.onDragOver.bind(this); + private readonly onDropHandler = this.onDrop.bind(this); + constructor() { const elementRef = inject(ElementRef); @@ -44,16 +48,16 @@ export class DropZoneDirective implements OnInit, OnDestroy { ngOnInit() { this.ngZone.runOutsideAngular(() => { - this.element.addEventListener('dragenter', this.onDragEnter.bind(this)); - this.element.addEventListener('dragover', this.onDragOver.bind(this)); - this.element.addEventListener('drop', this.onDrop.bind(this)); + this.element.addEventListener('dragenter', this.onDragEnterHandler); + this.element.addEventListener('dragover', this.onDragOverHandler); + this.element.addEventListener('drop', this.onDropHandler); }); } ngOnDestroy() { - this.element.removeEventListener('dragenter', this.onDragEnter); - this.element.removeEventListener('dragover', this.onDragOver); - this.element.removeEventListener('drop', this.onDrop); + this.element.removeEventListener('dragenter', this.onDragEnterHandler); + this.element.removeEventListener('dragover', this.onDragOverHandler); + this.element.removeEventListener('drop', this.onDropHandler); } onDragEnter(event: DragEvent) { From 77c549830fef1ca2c114fa600f49e1083ace8370 Mon Sep 17 00:00:00 2001 From: Tomasz Gnyp <49343696+tomgny@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:13:41 +0200 Subject: [PATCH 29/31] =?UTF-8?q?Revert=20"fix(core):=20fix=20ResizableDir?= =?UTF-8?q?ective=20memory=20leak=20from=20orphaned=20document=20=E2=80=A6?= =?UTF-8?q?"=20(#12201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1e40e5effd96c0ea77e886a792a67ca3c7fb955a. --- .../resizable/resizable.directive.spec.ts | 67 +++++-------------- .../resizable/resizable.directive.ts | 37 +++++++--- 2 files changed, 44 insertions(+), 60 deletions(-) diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts index 0e0b19140b..3317dfeeb2 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts @@ -16,7 +16,7 @@ */ import { TestBed } from '@angular/core/testing'; -import { ElementRef, EnvironmentInjector, NgZone, Renderer2, createEnvironmentInjector, runInInjectionContext } from '@angular/core'; +import { ElementRef, Injector, NgZone, Renderer2, runInInjectionContext } from '@angular/core'; import { ResizableDirective } from './resizable.directive'; describe('ResizableDirective', () => { @@ -24,7 +24,6 @@ describe('ResizableDirective', () => { let renderer: Renderer2; let element: ElementRef; let directive: ResizableDirective; - let testEnvInjector: EnvironmentInjector; const scrollTop = 0; const scrollLeft = 0; @@ -40,14 +39,8 @@ describe('ResizableDirective', () => { scrollLeft }; - let unlistenSpies: jasmine.Spy[]; - const rendererMock = { - listen: jasmine.createSpy('listen').and.callFake(() => { - const spy = jasmine.createSpy(`unlisten-${unlistenSpies.length}`); - unlistenSpies.push(spy); - return spy; - }), + listen: jasmine.createSpy('listen'), setStyle: jasmine.createSpy('setStyle') }; @@ -60,10 +53,6 @@ describe('ResizableDirective', () => { }; beforeEach(() => { - unlistenSpies = []; - rendererMock.listen.calls.reset(); - rendererMock.setStyle.calls.reset(); - TestBed.configureTestingModule({ imports: [ResizableDirective], providers: [ @@ -75,28 +64,29 @@ describe('ResizableDirective', () => { element = TestBed.inject(ElementRef); renderer = TestBed.inject(Renderer2); ngZone = TestBed.inject(NgZone); + const injector = TestBed.inject(Injector); spyOn(ngZone, 'runOutsideAngular').and.callFake((fn) => fn()); spyOn(ngZone, 'run').and.callFake((fn) => fn()); - testEnvInjector = createEnvironmentInjector( - [ + const testInjector = Injector.create({ + providers: [ { provide: Renderer2, useValue: renderer }, { provide: ElementRef, useValue: element }, { provide: NgZone, useValue: ngZone } ], - TestBed.inject(EnvironmentInjector) - ); + parent: injector + }); - directive = runInInjectionContext(testEnvInjector, () => new ResizableDirective()); + directive = runInInjectionContext(testInjector, () => new ResizableDirective()); directive.ngOnInit(); }); - it('should not attach any document listeners on init', () => { - expect(renderer.listen).not.toHaveBeenCalled(); + it('should attach mousedown event to document', () => { + expect(renderer.listen).toHaveBeenCalledWith('document', 'mousedown', jasmine.any(Function)); }); - it('should attach document mousemove listener only during active drag', () => { + it('should attach mousemove event to document', () => { const mouseDownEvent = new MouseEvent('mousedown'); directive.mousedown.next({ ...mouseDownEvent, resize: true }); @@ -104,6 +94,10 @@ describe('ResizableDirective', () => { expect(renderer.listen).toHaveBeenCalledWith('document', 'mousemove', jasmine.any(Function)); }); + it('should attach mouseup event to document', () => { + expect(renderer.listen).toHaveBeenCalledWith('document', 'mouseup', jasmine.any(Function)); + }); + it('should should set the cursor on mouse down', () => { spyOn(directive.resizeStart, 'emit'); const mouseDownEvent = new MouseEvent('mousedown'); @@ -180,35 +174,4 @@ describe('ResizableDirective', () => { expect(directive.keyboardResizing.emit).toHaveBeenCalledWith({ rectangle: { top: 0, left: 0, bottom: 0, right: step, width: step } }); }); - - it('should unregister document listeners on destroy', () => { - directive.mousedown.next({ ...new MouseEvent('mousedown'), resize: true }); - expect(unlistenSpies.length).toBeGreaterThan(0); - - testEnvInjector.destroy(); - unlistenSpies.forEach((spy) => expect(spy).toHaveBeenCalledTimes(1)); - }); - - it('should not accumulate mousemove listeners across repeated drag cycles', () => { - const listenCountAfterInit = rendererMock.listen.calls.count(); - - const mouseDownEvent = new MouseEvent('mousedown'); - const mouseUpEvent = new MouseEvent('mouseup'); - - directive.mousedown.next({ ...mouseDownEvent, resize: true }); - const listenCountAfterFirstDrag = rendererMock.listen.calls.count(); - expect(listenCountAfterFirstDrag).toBeGreaterThan(listenCountAfterInit); - - directive.mouseup.next(mouseUpEvent); - const unlistenedAfterFirstDrag = unlistenSpies.filter((spy) => spy.calls.count() > 0).length; - - directive.mousedown.next({ ...mouseDownEvent, resize: true }); - directive.mouseup.next(mouseUpEvent); - const unlistenedAfterSecondDrag = unlistenSpies.filter((spy) => spy.calls.count() > 0).length; - - expect(unlistenedAfterSecondDrag).toBeGreaterThan(unlistenedAfterFirstDrag); - - testEnvInjector.destroy(); - unlistenSpies.forEach((spy) => expect(spy).toHaveBeenCalledTimes(1)); - }); }); diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts index 9b7436759c..06d168a006 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts @@ -61,35 +61,53 @@ export class ResizableDirective implements OnInit, OnDestroy { mousemove = new Subject(); + private readonly pointerDown: Observable; private readonly pointerMove: Observable; + private readonly pointerUp: Observable; private startingRect: BoundingRectangle; private currentRect: BoundingRectangle; + private unsubscribeMouseDown?: () => void; + private unsubscribeMouseMove?: () => void; + private unsubscribeMouseUp?: () => void; + private readonly destroyRef = inject(DestroyRef); constructor() { const renderer = this.renderer; const zone = this.zone; - // Document-level mousemove is needed for smooth drag tracking when cursor leaves the handle element. - // Only subscribed during active drag via share() refcount. - this.pointerMove = new Observable((observer: Observer) => { - let stopListening: () => void = () => {}; + this.pointerDown = new Observable((observer: Observer) => { zone.runOutsideAngular(() => { - stopListening = renderer.listen('document', 'mousemove', (event: MouseEvent) => { + this.unsubscribeMouseDown = renderer.listen('document', 'mousedown', (event: MouseEvent) => { + observer.next(event); + }); + }); + }).pipe(share()); + + this.pointerMove = new Observable((observer: Observer) => { + zone.runOutsideAngular(() => { + this.unsubscribeMouseMove = renderer.listen('document', 'mousemove', (event: MouseEvent) => { + observer.next(event); + }); + }); + }).pipe(share()); + + this.pointerUp = new Observable((observer: Observer) => { + zone.runOutsideAngular(() => { + this.unsubscribeMouseUp = renderer.listen('document', 'mouseup', (event: MouseEvent) => { observer.next(event); }); }); - return stopListening; }).pipe(share()); } ngOnInit(): void { - const mousedown$ = this.mousedown.asObservable(); + const mousedown$ = merge(this.pointerDown, this.mousedown); const mousemove$ = merge(this.pointerMove, this.mousemove); - const mouseup$ = this.mouseup.asObservable(); + const mouseup$ = merge(this.pointerUp, this.mouseup); const mouseDrag: Observable = mousedown$ .pipe( @@ -166,6 +184,9 @@ export class ResizableDirective implements OnInit, OnDestroy { this.mousedown.complete(); this.mousemove.complete(); this.mouseup.complete(); + this.unsubscribeMouseDown?.(); + this.unsubscribeMouseMove?.(); + this.unsubscribeMouseUp?.(); } resizeByKeyboard(delta: number): void { From 62be76e6fa239b7212d28dba35ce54c43782e0c6 Mon Sep 17 00:00:00 2001 From: Ehsan Rezaei Date: Mon, 31 Aug 2026 15:04:23 +0200 Subject: [PATCH 30/31] AAE-49653 Migrating to batch count endpoint (#12186) * AAE-49653 Migrating to batch count endpoint * AAE-49653 Updating code with the latest BE contract * AAE-49653 Improving type safety * AAE-49653 Code improvement * AAE-49653 fixed the DI coupling and type safety * AAE-49653 Code improvements * AAE-49653 Adding more unit tests and fixing a subscription * AAE-49653 Updating the comment * AAE-49653 Code improvement * AAE-49653 Removing the non-batched counter fallback * AAE-49653 Making filter key mandatory string * AAE-49653 Removing dead code and improvements * Revert "AAE-49653 Removing dead code and improvements" This reverts commit a8fc99b0442db0ddd1f9523c67c3d97b560c5faf. * Revert "AAE-49653 Making filter key mandatory string" This reverts commit ba916b7c22b3ab90d5f280a9f613c4751b3bf0e1. * Revert "AAE-49653 Removing the non-batched counter fallback" This reverts commit 416a61fb9e6edc4ac09b56ba7e71b2744edb15e6. * AAE-49653 Putting implementation under FF * AAE-49653 Adding new input to filter components, moving FF handling to consumer app * AAE-49653 Removing extra comments --- .../process-filters-cloud.component.md | 1 + .../task-filters-cloud.component.md | 1 + .../lib/models/filter-counters-cloud.model.ts | 55 ++ .../process-filters-cloud.component.spec.ts | 563 +++++++++++++-- .../process-filters-cloud.component.ts | 237 ++++--- .../services/process-filter-cloud.service.ts | 6 + .../services/process-list-cloud.service.ts | 2 +- .../filter-counters-cloud.service.spec.ts | 651 ++++++++++++++++++ .../services/filter-counters-cloud.service.ts | 375 ++++++++++ .../services/notification-cloud.service.ts | 7 +- .../src/lib/services/public-api.ts | 1 + .../task-filters-cloud.component.spec.ts | 193 +++++- .../task-filters-cloud.component.ts | 157 +++-- .../services/task-filter-cloud.service.ts | 5 + .../services/task-list-cloud.service.ts | 2 +- lib/process-services-cloud/src/public-api.ts | 1 + 16 files changed, 2056 insertions(+), 201 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/models/filter-counters-cloud.model.ts create mode 100644 lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts diff --git a/docs/process-services-cloud/components/process-filters-cloud.component.md b/docs/process-services-cloud/components/process-filters-cloud.component.md index d960153d1d..31b3b4a6fe 100644 --- a/docs/process-services-cloud/components/process-filters-cloud.component.md +++ b/docs/process-services-cloud/components/process-filters-cloud.component.md @@ -27,6 +27,7 @@ Lists all available process filters and allows to select a filter. | appName | `string` | "" | (required) The application name | | filterParam | `UserTaskFilterRepresentation` | | (optional) The filter to be selected by default | | showIcons | `boolean` | false | (optional) Toggles showing an icon by the side of each filter | +| useBatchedCounters | `boolean` | false | Get all the filter counters with one call to `POST /query/v1/count` (needs Activiti 8.7.0). Turn it on for both filter components. | ### Events diff --git a/docs/process-services-cloud/components/task-filters-cloud.component.md b/docs/process-services-cloud/components/task-filters-cloud.component.md index d7580e5dea..acc85e1a68 100644 --- a/docs/process-services-cloud/components/task-filters-cloud.component.md +++ b/docs/process-services-cloud/components/task-filters-cloud.component.md @@ -36,6 +36,7 @@ Shows all available filters. | appName | `string` | "" | Display filters available to the current user for the application with the specified name. | | filterParam | `FilterParamsModel` | | Parameters to use for the task filter cloud. If there is no match then the default filter (the first one in the list) is selected. | | showIcons | `boolean` | false | Toggles display of the filter's icons. | +| useBatchedCounters | `boolean` | false | Get all the filter counters with one call to `POST /query/v1/count` (needs Activiti 8.7.0). Turn it on for both filter components. | ### Events diff --git a/lib/process-services-cloud/src/lib/models/filter-counters-cloud.model.ts b/lib/process-services-cloud/src/lib/models/filter-counters-cloud.model.ts new file mode 100644 index 0000000000..5725360d1a --- /dev/null +++ b/lib/process-services-cloud/src/lib/models/filter-counters-cloud.model.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright © 2005-2026 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 const FilterCounterEntityType = { + TASK: 'TASK', + PROCESS_INSTANCE: 'PROCESS_INSTANCE' +} as const; + +export type FilterCounterEntityType = (typeof FilterCounterEntityType)[keyof typeof FilterCounterEntityType]; + +export interface FilterCountersQuerySort { + field: string; + direction: string; + isProcessVariable: boolean; +} + +export interface FilterCountersQuery { + requestId: string; + status?: string[]; + assignee?: string[]; + sort?: FilterCountersQuerySort; + [criteria: string]: unknown; +} + +export type FilterCountersRequest = { + [entityType in FilterCounterEntityType]?: FilterCountersQuery[]; +}; + +export interface FilterCounterCandidate { + key?: string | null; + showCounter?: boolean; +} + +export type FilterCounters = { + [entityType in FilterCounterEntityType]?: { [requestId: string]: number }; +}; + +export interface FilterCountersResult { + counters: { [filterKey: string]: number }; + batched: boolean; +} diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts index 8c90e5f42a..b3ca4b78f7 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.spec.ts @@ -16,15 +16,15 @@ */ import { Component, SimpleChange } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; import { first, of, Subject, throwError } from 'rxjs'; import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service'; import { ProcessFiltersCloudComponent } from './process-filters-cloud.component'; import { By } from '@angular/platform-browser'; -import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; +import { PROCESS_FILTERS_SERVICE_TOKEN, TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service'; import { mockProcessFilters } from '../../mock/process-filters-cloud.mock'; -import { AppConfigService, AppConfigServiceMock } from '@alfresco/adf-core'; +import { AppConfigService, AppConfigServiceMock, NoopAuthModule } from '@alfresco/adf-core'; import { ProcessListCloudService } from '../../../process-list/services/process-list-cloud.service'; import { ApolloTestingModule } from 'apollo-angular/testing'; import { HarnessLoader } from '@angular/cdk/testing'; @@ -32,39 +32,39 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatIconHarness } from '@angular/material/icon/testing'; import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { RouterTestingHarness } from '@angular/router/testing'; -import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model'; +import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service'; +import { FilterCounterEntityType, FilterCountersResult } from '../../../../models/filter-counters-cloud.model'; +import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model'; @Component({ selector: 'adf-cloud-dummy', template: '' }) class DummyComponent {} const ProcessFilterCloudServiceMock = { getProcessFilters: () => of(mockProcessFilters), - getProcessNotificationSubscription: () => of([]), filterKeyToBeRefreshed$: of(mockProcessFilters[0].key) }; describe('ProcessFiltersCloudComponent', () => { let processFilterService: ProcessFilterCloudService; + let filterCountersService: FilterCountersCloudService; + let processListService: ProcessListCloudService; let component: ProcessFiltersCloudComponent; let fixture: ComponentFixture; let getProcessFiltersSpy: jasmine.Spy; - let getProcessNotificationSubscriptionSpy: jasmine.Spy; + let getFilterCountersSpy: jasmine.Spy; + let refreshFilterCountersSpy: jasmine.Spy; + let getProcessCounterSpy: jasmine.Spy; let loader: HarnessLoader; let router: Router; const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => { TestBed.configureTestingModule({ - imports: [ProcessFiltersCloudComponent, ApolloTestingModule], + imports: [NoopAuthModule, ProcessFiltersCloudComponent, ApolloTestingModule], providers: [ { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, + { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, { provide: AppConfigService, useClass: AppConfigServiceMock }, - { - provide: ProcessListCloudService, - useValue: { - getProcessCounter: () => of(10), - getProcessListCount: () => of(10) - } - }, + ProcessListCloudService, { provide: ProcessFilterCloudService, useValue: ProcessFilterCloudServiceMock }, provideRouter([{ path: 'process-list-cloud', component: DummyComponent }]), { @@ -88,11 +88,16 @@ describe('ProcessFiltersCloudComponent', () => { component.searchApiMethod = searchApiMethod; processFilterService = TestBed.inject(ProcessFilterCloudService); + filterCountersService = TestBed.inject(FilterCountersCloudService); + processListService = TestBed.inject(ProcessListCloudService); TestBed.inject(ActivatedRoute); router = TestBed.inject(Router); await RouterTestingHarness.create(); - getProcessFiltersSpy = spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(mockProcessFilters)); - getProcessNotificationSubscriptionSpy = spyOn(processFilterService, 'getProcessNotificationSubscription').and.returnValue(of([])); + getProcessFiltersSpy = spyOn(filterCountersService, 'getProcessFilters').and.returnValue(of(mockProcessFilters)); + getFilterCountersSpy = spyOn(filterCountersService, 'getFilterCounters').and.returnValue(of({ counters: {}, batched: true })); + refreshFilterCountersSpy = spyOn(filterCountersService, 'refreshFilterCounters'); + getProcessCounterSpy = spyOn(processListService, 'getProcessCounter').and.returnValue(of(10)); + spyOn(processListService, 'getProcessListCount').and.returnValue(of(10)); }; const bindAppName = async (appName = 'my-app-1') => { @@ -463,17 +468,98 @@ describe('ProcessFiltersCloudComponent', () => { expect(component.updatedFiltersSet.has(filterKeyTest)).toBeFalsy(); }); - it('should call fetchProcessFilterCounter only if filter.showCounter is true', () => { - const filterWithCounter = { ...mockProcessFilters[0], showCounter: true }; - const filterWithoutCounter = { ...mockProcessFilters[1], showCounter: false }; - const fetchSpy = spyOn(component, 'fetchProcessFilterCounter').and.returnValue(of(42)); + it('should resolve the counter only of the filters with a counter enabled', () => { + const filterWithCounter = new ProcessFilterCloudModel({ ...mockProcessFilters[1], showCounter: true }); + const filterWithoutCounter = new ProcessFilterCloudModel({ ...mockProcessFilters[2], showCounter: false }); + getProcessCounterSpy.calls.reset(); component.filters = [filterWithCounter, filterWithoutCounter]; component.updateFilterCounters(); - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(filterWithCounter); - expect(fetchSpy).not.toHaveBeenCalledWith(filterWithoutCounter); + expect(getProcessCounterSpy).toHaveBeenCalledTimes(1); + expect(getProcessCounterSpy).toHaveBeenCalledWith(filterWithCounter.appName, filterWithCounter.status); + }); + + describe('Batched counters', () => { + beforeEach(() => { + getProcessFiltersSpy.and.returnValue( + of(mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true }))) + ); + }); + + it('should read the counters of the process filters of the bound app', async () => { + await bindAppName('mock-app-name'); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, false); + }); + + it('should not ask for the batched count endpoint by default', async () => { + await bindAppName('mock-app-name'); + + expect(component.useBatchedCounters).toBeFalse(); + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, false); + }); + + it('should ask for the batched count endpoint when the input is set', async () => { + fixture.componentRef.setInput('useBatchedCounters', true); + + await bindAppName('mock-app-name'); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, true); + }); + + it('should hold the counters resolved by the batched count request', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + }); + + it('should emit the filters whose counter changed', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); + + await bindAppName('mock-app-name'); + + expect(updatedFilterSpy).toHaveBeenCalledWith('FakeRunningProcesses'); + }); + + it('should resolve the counters one filter at a time when the batched endpoint is not available', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); + + await bindAppName('mock-app-name'); + + expect(getProcessCounterSpy).toHaveBeenCalledTimes(3); + expect(component.counters['FakeRunningProcesses']).toBe(10); + }); + + it('should resolve the counters of the filters the batch left out on their own', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(getProcessCounterSpy.calls.allArgs().map(([, status]) => status)).toEqual([null, 'COMPLETED']); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + getProcessCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(component.counters['completed-processes']).toBe(0); + }); + + it('should refresh the counters of every filter when a filter is clicked', async () => { + await bindAppName('mock-app-name'); + + component.onFilterClick(mockProcessFilters[1]); + + expect(refreshFilterCountersSpy).toHaveBeenCalledWith('mock-app-name'); + }); }); describe('Notifications config', () => { @@ -507,39 +593,436 @@ describe('ProcessFiltersCloudComponent', () => { expect(component.notificationDebounceTime).toBe(5000); }); - it('should debounce notification subscription using the configured debounce time', fakeAsync(() => { - const notifications$ = new Subject(); - getProcessNotificationSubscriptionSpy.and.returnValue(notifications$.asObservable()); + it('should keep the counters in sync with the counters stream', fakeAsync(() => { + const counters$ = new Subject(); + getFilterCountersSpy.and.returnValue(counters$.asObservable()); component.appName = 'mock-app-name'; fixture.detectChanges(); + component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true })); - const updateFilterCountersSpy = spyOn(component, 'updateFilterCounters'); - - notifications$.next([]); - tick(1000); - expect(updateFilterCountersSpy).not.toHaveBeenCalled(); - - tick(2000); - expect(updateFilterCountersSpy).toHaveBeenCalledTimes(1); + counters$.next({ counters: { FakeRunningProcesses: 7 }, batched: true }); + expect(component.counters['FakeRunningProcesses']).toBe(7); flush(); })); }); describe('Highlight Selected Filter', () => { - it('should make subscription', async () => { + const allProcessesFilterKey = mockProcessFilters[0].key; + const allProcessesFilterId = mockProcessFilters[0].id; + + it('should apply active CSS class on filter click', async () => { component.enableNotifications = true; await bindAppName('mock-app-name'); - expect(getProcessNotificationSubscriptionSpy).toHaveBeenCalled(); + + let link = fixture.debugElement.query(By.css(`[data-automation-id="${allProcessesFilterKey}_filter"]`)).nativeElement; + expect(link.getAttribute('href')).toBe(`/process-list-cloud?filterId=${allProcessesFilterId}`); + + link.click(); + fixture.detectChanges(); + await fixture.whenStable(); + expect(router.url).toBe(`/process-list-cloud?filterId=${allProcessesFilterId}`); + + link = fixture.debugElement.query(By.css(`[data-automation-id="${allProcessesFilterKey}_filter"]`)).nativeElement; + expect(link.classList).toContain('adf-active'); }); - it('should not make subscription when notifications are disabled', async () => { - const appConfigService = TestBed.inject(AppConfigService); - spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => (key === 'notifications' ? false : defaultValue)); + it('should add aria-current attribute with value "page" to the active filter', async () => { + component.enableNotifications = true; await bindAppName('mock-app-name'); - expect(getProcessNotificationSubscriptionSpy).not.toHaveBeenCalled(); + const link = fixture.debugElement.query(By.css(`[data-automation-id="${allProcessesFilterKey}_filter"]`)).nativeElement; + expect(link.getAttribute('aria-current')).toBe('page'); + }); + + it('should not have aria-current attribute when filter is not active', async () => { + component.enableNotifications = true; + await bindAppName('mock-app-name'); + + const link = fixture.debugElement.query(By.css(`[data-automation-id="${mockProcessFilters[1].key}_filter"]`)).nativeElement; + expect(link.getAttribute('aria-current')).toBeNull(); + }); + }); + }); + + describe('searchApiMethod set to POST', () => { + beforeEach(async () => { + await configureTestingModule('POST'); + }); + + it('should attach specific icon for each filter if hasIcon is true', async () => { + await bindAppName(); + + component.showIcons = true; + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.filters.length).toBe(3); + const filterIcons = await loader.getAllHarnesses(MatIconHarness.with({ selector: '[data-automation-id="adf-filter-icon"]' })); + expect(filterIcons.length).toBe(3); + expect(await filterIcons[0].getName()).toContain('adjust'); + expect(await filterIcons[1].getName()).toContain('inbox'); + expect(await filterIcons[2].getName()).toContain('done'); + }); + + it('should not attach icons for each filter if hasIcon is false', async () => { + component.showIcons = false; + await bindAppName(); + + const filterIcons = await loader.getAllHarnesses(MatIconHarness.with({ selector: '[data-automation-id="adf-filter-icon"]' })); + expect(filterIcons.length).toBe(0); + }); + + it('should display the filters', async () => { + await bindAppName(); + + component.showIcons = true; + + fixture.detectChanges(); + await fixture.whenStable(); + + const filters = fixture.debugElement.queryAll(By.css('.adf-process-filters__entry')); + expect(component.filters.length).toBe(3); + expect(filters.length).toBe(3); + expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses'); + expect(filters[1].nativeElement.innerText).toContain('FakeRunningProcesses'); + expect(filters[2].nativeElement.innerText).toContain('FakeCompletedProcesses'); + expect(Object.keys(component.counters).length).toBe(3); + }); + + it('should emit success with the filters when filters are loaded', async () => { + const successSpy = spyOn(component.success, 'emit'); + await bindAppName(); + + expect(successSpy).toHaveBeenCalledWith(mockProcessFilters); + expect(component.filters).toBeDefined(); + expect(component.filters[0].name).toEqual('FakeAllProcesses'); + expect(component.filters[1].name).toEqual('FakeRunningProcesses'); + expect(component.filters[2].name).toEqual('FakeCompletedProcesses'); + expect(Object.keys(component.counters).length).toBe(3); + }); + + it('should not select any filter as default', async () => { + await bindAppName(); + + expect(component.currentFilter).toBeUndefined(); + }); + + it('should filterClicked emit when a filter is clicked from the UI', async () => { + const filterClickedSpy = spyOn(component.filterClicked, 'emit'); + await bindAppName(); + + const filterButton = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${mockProcessFilters[0].key}_filter"]`); + filterButton.click(); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.currentFilter).toEqual(mockProcessFilters[0]); + expect(filterClickedSpy).toHaveBeenCalledWith(mockProcessFilters[0]); + }); + }); + + describe('API agnostic', () => { + beforeEach(async () => { + await configureTestingModule('GET'); + }); + + it('should emit an error with a bad response', async () => { + getProcessFiltersSpy.and.returnValue(throwError('wrong request')); + let lastValue: any; + component.error.subscribe((err) => (lastValue = err)); + + await bindAppName(); + + expect(lastValue).toBeDefined(); + }); + + it('should not select any process filter if filter input does not exist', async () => { + const change = new SimpleChange(null, { name: 'nonexistentFilter' }, true); + fixture.detectChanges(); + await fixture.whenStable(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toBeUndefined(); + }); + + it('should select the filter based on the input by name param', async () => { + const filterSelectedSpy = spyOn(component.filterSelected, 'emit'); + const change = new SimpleChange(null, { name: 'FakeRunningProcesses' }, true); + + await bindAppName(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toEqual(mockProcessFilters[1]); + expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[1]); + }); + + it('should select the filter based on the input by key param', async () => { + const filterSelectedSpy = spyOn(component.filterSelected, 'emit'); + const change = new SimpleChange(null, { key: 'completed-processes' }, true); + + await bindAppName(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toEqual(mockProcessFilters[2]); + expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]); + }); + + it('should select the filter based on the input by index param', async () => { + const filterSelectedSpy = spyOn(component.filterSelected, 'emit'); + const change = new SimpleChange(null, { index: 2 }, true); + + await bindAppName(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toEqual(mockProcessFilters[2]); + expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]); + }); + + it('should select the filter based on the input by id param', async () => { + const filterSelectedSpy = spyOn(component.filterSelected, 'emit'); + const change = new SimpleChange(null, { id: '12' }, true); + + await bindAppName(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toEqual(mockProcessFilters[2]); + expect(filterSelectedSpy).toHaveBeenCalledWith(mockProcessFilters[2]); + }); + + it('should reset the filter when the param is undefined', () => { + const change = new SimpleChange(mockProcessFilters[0], undefined, false); + component.currentFilter = mockProcessFilters[0]; + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toEqual(undefined); + }); + + it('should not emit a filter clicked event when a filter is selected through the filterParam input (filterClicked emits only through a UI click action)', async () => { + const filterClickedSpy = spyOn(component.filterClicked, 'emit'); + const change = new SimpleChange(null, { id: '10' }, true); + + await bindAppName(); + component.ngOnChanges({ filterParam: change }); + + expect(component.currentFilter).toBe(mockProcessFilters[0]); + expect(filterClickedSpy).not.toHaveBeenCalled(); + }); + + it('should reload filters by appName on binding changes', () => { + spyOn(component, 'getFilters').and.stub(); + const appName = 'my-app-1'; + + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ appName: change }); + + expect(component.getFilters).toHaveBeenCalledWith(appName); + }); + + it('should not reload filters by appName null on binding changes', () => { + spyOn(component, 'getFilters').and.stub(); + const appName = null; + + const change = new SimpleChange(undefined, appName, true); + component.ngOnChanges({ appName: change }); + + expect(component.getFilters).not.toHaveBeenCalledWith(appName); + }); + + it('should reload filters by app name on binding changes', () => { + spyOn(component, 'getFilters').and.stub(); + const appName = 'fake-app-name'; + + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ appName: change }); + + expect(component.getFilters).toHaveBeenCalledWith(appName); + }); + + it('should return the current filter after one is selected', () => { + const filter = mockProcessFilters[1]; + component.filters = mockProcessFilters; + + expect(component.currentFilter).toBeUndefined(); + component.selectFilter({ id: filter.id }); + expect(component.getCurrentFilter()).toBe(filter); + }); + + it('should remove key from set of updated filters when received refreshed filter key', async () => { + const filterKeyTest = 'filter-key-test'; + component.updatedFiltersSet.add(filterKeyTest); + + expect(component.updatedFiltersSet.size).toBe(1); + processFilterService.filterKeyToBeRefreshed$ = of(filterKeyTest); + fixture.detectChanges(); + + expect(component.updatedFiltersSet.has(filterKeyTest)).toBeFalsy(); + }); + + it('should resolve the counter only of the filters with a counter enabled', () => { + const filterWithCounter = new ProcessFilterCloudModel({ ...mockProcessFilters[1], showCounter: true }); + const filterWithoutCounter = new ProcessFilterCloudModel({ ...mockProcessFilters[2], showCounter: false }); + getProcessCounterSpy.calls.reset(); + + component.filters = [filterWithCounter, filterWithoutCounter]; + component.updateFilterCounters(); + + expect(getProcessCounterSpy).toHaveBeenCalledTimes(1); + expect(getProcessCounterSpy).toHaveBeenCalledWith(filterWithCounter.appName, filterWithCounter.status); + }); + + describe('Batched counters', () => { + beforeEach(() => { + getProcessFiltersSpy.and.returnValue( + of(mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true }))) + ); + }); + + it('should read the counters of the process filters of the bound app', async () => { + await bindAppName('mock-app-name'); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, false); + }); + + it('should not ask for the batched count endpoint by default', async () => { + await bindAppName('mock-app-name'); + + expect(component.useBatchedCounters).toBeFalse(); + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, false); + }); + + it('should ask for the batched count endpoint when the input is set', async () => { + fixture.componentRef.setInput('useBatchedCounters', true); + + await bindAppName('mock-app-name'); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, true); + }); + + it('should hold the counters resolved by the batched count request', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + }); + + it('should emit the filters whose counter changed', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); + + await bindAppName('mock-app-name'); + + expect(updatedFilterSpy).toHaveBeenCalledWith('FakeRunningProcesses'); + }); + + it('should resolve the counters one filter at a time when the batched endpoint is not available', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); + + await bindAppName('mock-app-name'); + + expect(getProcessCounterSpy).toHaveBeenCalledTimes(3); + expect(component.counters['FakeRunningProcesses']).toBe(10); + }); + + it('should resolve the counters of the filters the batch left out on their own', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(getProcessCounterSpy.calls.allArgs().map(([, status]) => status)).toEqual([null, 'COMPLETED']); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { FakeRunningProcesses: 9 }, batched: true })); + getProcessCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName('mock-app-name'); + + expect(component.counters['FakeRunningProcesses']).toBe(9); + expect(component.counters['completed-processes']).toBe(0); + }); + + it('should refresh the counters of every filter when a filter is clicked', async () => { + await bindAppName('mock-app-name'); + + component.onFilterClick(mockProcessFilters[1]); + + expect(refreshFilterCountersSpy).toHaveBeenCalledWith('mock-app-name'); + }); + }); + + describe('Notifications config', () => { + it('should read enableNotifications and notificationDebounceTime from app config on init', () => { + const appConfigService = TestBed.inject(AppConfigService); + const getSpy = spyOn(appConfigService, 'get').and.callThrough(); + + fixture.detectChanges(); + + expect(getSpy).toHaveBeenCalledWith('notifications', true); + expect(getSpy).toHaveBeenCalledWith('notificationDebounceTime', 3000); + }); + + it('should default notificationDebounceTime to 3000 when not set in app config', () => { + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(3000); + }); + + it('should use notificationDebounceTime from app config', () => { + const appConfigService: AppConfigService = TestBed.inject(AppConfigService); + spyOn(appConfigService, 'get').and.callFake((key: string, defaultValue: any) => { + if (key === 'notificationDebounceTime') { + return 5000; + } + return defaultValue; + }); + + fixture.detectChanges(); + + expect(component.notificationDebounceTime).toBe(5000); + }); + + it('should keep the counters in sync with the counters stream', fakeAsync(() => { + const counters$ = new Subject(); + getFilterCountersSpy.and.returnValue(counters$.asObservable()); + component.appName = 'mock-app-name'; + + fixture.detectChanges(); + component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true })); + + counters$.next({ counters: { FakeRunningProcesses: 7 }, batched: true }); + + expect(component.counters['FakeRunningProcesses']).toBe(7); + flush(); + })); + + it('should resolve the counters one filter at a time when the batched endpoint is not available', fakeAsync(() => { + const counters$ = new Subject(); + getFilterCountersSpy.and.returnValue(counters$.asObservable()); + component.appName = 'mock-app-name'; + + fixture.detectChanges(); + component.filters = mockProcessFilters.map((filter) => new ProcessFilterCloudModel({ ...filter, showCounter: true })); + getProcessCounterSpy.calls.reset(); + + counters$.next({ counters: {}, batched: false }); + + expect(getProcessCounterSpy).toHaveBeenCalledTimes(3); + flush(); + })); + }); + + describe('Highlight Selected Filter', () => { + it('should read the counters of the bound app', async () => { + component.enableNotifications = true; + await bindAppName('mock-app-name'); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('mock-app-name', FilterCounterEntityType.PROCESS_INSTANCE, false); }); it('should emit filter key when filter counter is set for first time', () => { diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts index 9e7fc7ba5f..d7001220d4 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters/process-filters-cloud.component.ts @@ -16,14 +16,16 @@ */ import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; -import { EMPTY, Observable } from 'rxjs'; +import { combineLatest, defer, EMPTY, Observable, of, Subscription } from 'rxjs'; import { ProcessFilterCloudService } from '../../services/process-filter-cloud.service'; import { ProcessFilterCloudModel } from '../../models/process-filter-cloud.model'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; import { FilterParamsModel } from '../../../../task/task-filters/models/filter-cloud.model'; -import { catchError, debounceTime, map, shareReplay, tap } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { ProcessListCloudService } from '../../../process-list/services/process-list-cloud.service'; import { ProcessFilterCloudAdapter } from '../../../process-list/models/process-cloud-query-request.model'; +import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service'; +import { FilterCounterEntityType } from '../../../../models/filter-counters-cloud.model'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { TranslatePipe } from '@ngx-translate/core'; import { AsyncPipe } from '@angular/common'; @@ -43,10 +45,21 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { @Input() appName: string = ''; - /** (optional) From Activiti 8.7.0 forward, use the 'POST' method to get the process count */ + /** + * (optional) From Activiti 8.7.0 forward, use the 'POST' method to get the process count. + * + */ @Input() searchApiMethod: 'GET' | 'POST' = 'GET'; + /** + * (optional) Resolves the counters of the task and the process filters with a single call to + * `POST /query/v1/count`. Both filter components have to + * ask for it, otherwise the counters are resolved one filter at a time. + */ + @Input() + useBatchedCounters = false; + /** (optional) The filter to be selected by default */ @Input() filterParam: FilterParamsModel; @@ -79,27 +92,31 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { currentFilter?: ProcessFilterCloudModel; filters: ProcessFilterCloudModel[] = []; counters: { [key: string]: number } = {}; - enableNotifications = true; - notificationDebounceTime = 3000; currentFiltersValues: { [key: string]: number } = {}; updatedFiltersSet = new Set(); + enableNotifications = true; + notificationDebounceTime = 3000; private filtersLoadedFor?: string; + private countersSubscription?: Subscription; + private countersFilters$?: Observable; + private batchedCounters = true; private readonly destroyRef = inject(DestroyRef); private readonly processFilterCloudService = inject(ProcessFilterCloudService); private readonly translationService = inject(TranslationService); private readonly appConfigService = inject(AppConfigService); private readonly processListCloudService = inject(ProcessListCloudService); + private readonly filterCountersCloudService = inject(FilterCountersCloudService); private readonly activatedRoute = inject(ActivatedRoute); protected readonly currentRouteFilterId = toSignal(this.activatedRoute.queryParamMap.pipe(map((params) => params.get('filterId')))); ngOnInit() { this.enableNotifications = this.appConfigService.get('notifications', true); this.notificationDebounceTime = this.appConfigService.get('notificationDebounceTime', 3000); + if (!this.filtersLoadedFor) { this.getFilters(this.appName); } - this.initProcessNotification(); this.getFilterKeysAfterExternalRefreshing(); } @@ -110,6 +127,8 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { this.getFilters(appName.currentValue); } else if (filter && filter.currentValue !== filter.previousValue) { this.selectFilterAndEmit(filter.currentValue); + } else if (changes['useBatchedCounters'] && !changes['useBatchedCounters'].firstChange && this.filtersLoadedFor) { + this.loadFilterCounters(this.filtersLoadedFor); } } @@ -120,8 +139,8 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { */ getFilters(appName: string): void { this.filtersLoadedFor = appName; - const filters$ = this.processFilterCloudService.getProcessFilters(appName).pipe(shareReplay({ bufferSize: 1, refCount: true })); - this.filters$ = filters$.pipe(catchError(() => EMPTY)); + const filters$ = this.filterCountersCloudService.getProcessFilters(appName); + this.filters$ = filters$.pipe(catchError(() => of([]))); filters$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: (res) => { @@ -130,19 +149,25 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { this.initFilterCounters(); this.selectFilterAndEmit(this.filterParam); this.success.emit(res); - this.updateFilterCounters(); }, - error: (err: any) => { + error: (err: unknown) => { this.error.emit(err); } }); + + this.countersFilters$ = filters$; + this.loadFilterCounters(appName); } /** * Initialize counter collection for filters */ - initFilterCounters() { - this.filters.forEach((filter) => (this.counters[filter.key] = 0)); + initFilterCounters(): void { + this.filters.forEach((filter) => { + if (filter.key) { + this.counters[filter.key] = 0; + } + }); } /** @@ -167,20 +192,6 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { ); // fallback to preserve the previous behavior } - /** - * Check equality of the filter names by translating the given name strings - * - * @param name1 source name - * @param name2 target name - * @returns `true` if filter names are equal, otherwise `false` - */ - private checkFilterNamesEquality(name1: string, name2: string): boolean { - const translatedName1 = this.translationService.instant(name1); - const translatedName2 = this.translationService.instant(name2); - - return translatedName1.toLocaleLowerCase() === translatedName2.toLocaleLowerCase(); - } - /** * Selects and emits the given filter * @@ -213,7 +224,7 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { if (filter) { this.selectFilter(filter); this.filterClicked.emit(this.currentFilter); - this.updateFilterCounter(this.currentFilter); + this.refreshFilterCounter(this.currentFilter); this.updatedFiltersSet.delete(filter.key); } else { this.currentFilter = undefined; @@ -247,6 +258,83 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { return this.filters === undefined || (this.filters && this.filters.length === 0); } + isActiveFilter(filter: ProcessFilterCloudModel): boolean { + return this.currentFilter.name === filter.name; + } + + /** + * @deprecated does nothing: the counters keep themselves in sync. Removed in ADF 10.0.0. + */ + initProcessNotification(): void {} + + /** + * Iterate over filters and update counters + * + * @deprecated counts one filter at a time. Removed in ADF 10.0.0. + */ + updateFilterCounters(): void { + this.filters.forEach((filter) => this.updateFilterCounter(filter)); + } + + /** + * Get current value for filter and check if value has changed + * + * @param filter filter + * @deprecated counts one filter at a time. Removed in ADF 10.0.0. + */ + updateFilterCounter(filter: ProcessFilterCloudModel): void { + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { + return; + } + + defer(() => this.fetchProcessFilterCounter(filter)) + .pipe( + catchError(() => EMPTY), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((counter) => { + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); + this.counters = { ...this.counters, [filterKey]: counter }; + }); + } + + checkIfFilterValuesHasBeenUpdated(filterKey: string, filterValue: number): void { + if (this.currentFiltersValues[filterKey] === undefined || this.currentFiltersValues[filterKey] !== filterValue) { + this.currentFiltersValues = { ...this.currentFiltersValues, [filterKey]: filterValue }; + this.updatedFilter.emit(filterKey); + this.updatedFiltersSet.add(filterKey); + } + } + + /** + * Get filer key when filter was refreshed by external action + * + */ + getFilterKeysAfterExternalRefreshing(): void { + this.processFilterCloudService.filterKeyToBeRefreshed$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterKey: string) => this.updatedFiltersSet.delete(filterKey)); + } + + isFilterUpdated(filterName: string): boolean { + return this.updatedFiltersSet.has(filterName); + } + + /** + * Check equality of the filter names by translating the given name strings + * + * @param name1 source name + * @param name2 target name + * @returns `true` if filter names are equal, otherwise `false` + */ + private checkFilterNamesEquality(name1: string, name2: string): boolean { + const translatedName1 = this.translationService.instant(name1); + const translatedName2 = this.translationService.instant(name2); + + return translatedName1.toLocaleLowerCase() === translatedName2.toLocaleLowerCase(); + } + /** * Reset the filters */ @@ -255,76 +343,53 @@ export class ProcessFiltersCloudComponent implements OnInit, OnChanges { this.currentFilter = undefined; } - isActiveFilter(filter: ProcessFilterCloudModel): boolean { - return this.currentFilter.name === filter.name; - } - - initProcessNotification(): void { - if (this.appName && this.enableNotifications) { - this.processFilterCloudService - .getProcessNotificationSubscription(this.appName) - .pipe(debounceTime(this.notificationDebounceTime), takeUntilDestroyed(this.destroyRef)) - .subscribe(() => { - this.updateFilterCounters(); - }); - } - } - - /** - * Iterate over filters and update counters - */ - updateFilterCounters(): void { - this.filters.forEach((filter: ProcessFilterCloudModel) => { - this.updateFilterCounter(filter); - }); - } - - /** - * Get current value for filter and check if value has changed - * - * @param filter filter - */ - updateFilterCounter(filter: ProcessFilterCloudModel): void { - if (!filter?.showCounter) { + private loadFilterCounters(appName: string): void { + if (!this.countersFilters$) { return; } - this.fetchProcessFilterCounter(filter) - .pipe( - tap((filterCounter) => { - this.checkIfFilterValuesHasBeenUpdated(filter.key, filterCounter); - }) - ) - .subscribe((data) => { - this.counters = { - ...this.counters, - [filter.key]: data - }; + this.countersSubscription?.unsubscribe(); + this.countersSubscription = combineLatest([ + this.countersFilters$.pipe(catchError(() => of([]))), + this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.PROCESS_INSTANCE, this.useBatchedCounters) + ]) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(([, { counters, batched }]) => { + this.batchedCounters = batched; + if (batched) { + this.applyFilterCounters(counters); + } else { + this.updateFilterCounters(); + } }); } - checkIfFilterValuesHasBeenUpdated(filterKey: string, filterValue: number): void { - if (this.currentFiltersValues[filterKey] === undefined || this.currentFiltersValues[filterKey] !== filterValue) { - this.currentFiltersValues[filterKey] = filterValue; - this.updatedFilter.emit(filterKey); - this.updatedFiltersSet.add(filterKey); - } - } + private applyFilterCounters(counters: { [filterKey: string]: number }): void { + this.filters.forEach((filter) => { + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { + return; + } - isFilterUpdated(filterName: string): boolean { - return this.updatedFiltersSet.has(filterName); - } + const counter = counters[filterKey]; + if (counter === undefined) { + this.updateFilterCounter(filter); + return; + } - /** - * Get filer key when filter was refreshed by external action - * - */ - getFilterKeysAfterExternalRefreshing(): void { - this.processFilterCloudService.filterKeyToBeRefreshed$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((filterKey: string) => { - this.updatedFiltersSet.delete(filterKey); + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); + this.counters = { ...this.counters, [filterKey]: counter }; }); } + private refreshFilterCounter(filter?: ProcessFilterCloudModel): void { + if (this.batchedCounters) { + this.filterCountersCloudService.refreshFilterCounters(this.appName); + } else if (filter) { + this.updateFilterCounter(filter); + } + } + private fetchProcessFilterCounter(filter: ProcessFilterCloudModel): Observable { return this.searchApiMethod === 'POST' ? this.processListCloudService.getProcessListCount(new ProcessFilterCloudAdapter(filter)) diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts index 316933cba6..9839470ea2 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts @@ -404,6 +404,12 @@ export class ProcessFilterCloudService { ]; } + /** + * @deprecated use FilterCountersCloudService.getEngineEvents instead. + * + * @param appName Name of the target app + * @returns Process engine events + */ getProcessNotificationSubscription(appName: string): Observable { return this.notificationCloudService .makeGQLQuery(appName, PROCESS_EVENT_SUBSCRIPTION_QUERY) diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts index 84c5a97cfb..8316bf1f09 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts @@ -100,7 +100,7 @@ export class ProcessListCloudService extends BaseCloudService { ); } - protected buildQueryData(requestNode: ProcessListRequestModel): { [key: string]: any } { + buildQueryData(requestNode: ProcessListRequestModel): { [key: string]: any } { const queryData: { [key: string]: any } = { name: requestNode.name, id: requestNode.id, diff --git a/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.spec.ts new file mode 100644 index 0000000000..c0846418f6 --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.spec.ts @@ -0,0 +1,651 @@ +/*! + * @license + * Copyright © 2005-2026 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 { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core'; +import { BehaviorSubject, combineLatest, firstValueFrom, Observable, of, Subject, throwError } from 'rxjs'; +import { ApolloTestingModule } from 'apollo-angular/testing'; +import { FilterCountersCloudService } from './filter-counters-cloud.service'; +import { NotificationCloudService } from './notification-cloud.service'; +import { LocalPreferenceCloudService } from './local-preference-cloud.service'; +import { PROCESS_FILTERS_SERVICE_TOKEN, TASK_FILTERS_SERVICE_TOKEN } from './cloud-token.service'; +import { TaskFilterCloudService } from '../task/task-filters/services/task-filter-cloud.service'; +import { ProcessFilterCloudService } from '../process/process-filters/services/process-filter-cloud.service'; +import { TaskFilterCloudModel } from '../task/task-filters/models/filter-cloud.model'; +import { ProcessFilterCloudModel } from '../process/process-filters/models/process-filter-cloud.model'; +import { + FilterCounterEntityType, + FilterCounters, + FilterCountersQuery, + FilterCountersRequest, + FilterCountersResult +} from '../models/filter-counters-cloud.model'; +import { TaskCloudEngineEvent } from '../models/engine-event-cloud.model'; +import { FetchResult } from '@apollo/client/core'; + +type EngineEventsResult = FetchResult<{ engineEvents?: TaskCloudEngineEvent[] }>; + +interface CountEndpoint { + post: (url: string, request: FilterCountersRequest) => Observable; +} + +describe('FilterCountersCloudService', () => { + let service: FilterCountersCloudService; + let notificationCloudService: NotificationCloudService; + let appConfigService: AppConfigService; + let taskEvents$: Subject; + let processEvents$: Subject; + let makeGQLQuerySpy: jasmine.Spy; + let postSpy: jasmine.Spy; + let getTaskListFiltersSpy: jasmine.Spy; + let getProcessFiltersSpy: jasmine.Spy; + + const countRequest = (): FilterCountersRequest => postSpy.calls.mostRecent().args[1]; + const countUrl = (): string => postSpy.calls.mostRecent().args[0]; + const countQueries = (entityType: FilterCounterEntityType): FilterCountersQuery[] => countRequest()[entityType] ?? []; + const countRequestIds = (entityType: FilterCounterEntityType): string[] => countQueries(entityType).map((query) => query.requestId); + + const countersMock: FilterCounters = { + TASK: { 'my-tasks': 5, 'queued-tasks': 0 }, + PROCESS_INSTANCE: { 'running-processes': 5 } + }; + + const taskFilter = (filter: Partial) => + new TaskFilterCloudModel({ appName: 'mock-app', sort: 'createdDate', order: 'DESC', ...filter }); + const processFilter = (filter: Partial) => + new ProcessFilterCloudModel({ appName: 'mock-app', sort: 'startDate', order: 'DESC', ...filter }); + + const taskFiltersMock = [ + taskFilter({ key: 'my-tasks', status: 'ASSIGNED', assignee: 'mock-user', showCounter: true }), + taskFilter({ key: 'queued-tasks', status: 'CREATED', showCounter: true }), + taskFilter({ key: 'completed-tasks', status: 'COMPLETED', showCounter: false }) + ]; + const processFiltersMock = [ + processFilter({ key: 'running-processes', status: 'RUNNING', showCounter: true }), + processFilter({ key: 'all-processes', status: '', showCounter: false }) + ]; + + const engineEvents = (eventType: string): EngineEventsResult => ({ + data: { engineEvents: [{ eventType, entity: {} } as TaskCloudEngineEvent] } + }); + const emitTaskEvent = (eventType = 'TASK_CREATED') => taskEvents$.next(engineEvents(eventType)); + const emitProcessEvent = (eventType = 'PROCESS_STARTED') => processEvents$.next(engineEvents(eventType)); + + const counters = (entityType: FilterCounterEntityType, appName = 'mock-app') => + firstValueFrom(service.getFilterCounters(appName, entityType, true)); + const taskCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.TASK, appName); + const processCounters = (appName = 'mock-app') => counters(FilterCounterEntityType.PROCESS_INSTANCE, appName); + const bothCounters = (appName = 'mock-app') => + firstValueFrom( + combineLatest([ + service.getFilterCounters(appName, FilterCounterEntityType.TASK, true), + service.getFilterCounters(appName, FilterCounterEntityType.PROCESS_INSTANCE, true) + ]) + ); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopAuthModule, ApolloTestingModule], + providers: [ + { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, + { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService } + ] + }); + + service = TestBed.inject(FilterCountersCloudService); + notificationCloudService = TestBed.inject(NotificationCloudService); + appConfigService = TestBed.inject(AppConfigService); + appConfigService.config.bpmHost = 'https://fake-bpm-host.com'; + + taskEvents$ = new Subject(); + processEvents$ = new Subject(); + makeGQLQuerySpy = spyOn(notificationCloudService, 'makeGQLQuery'); + makeGQLQuerySpy.and.callFake((_appName: string, query: string) => + (query.includes('TASK_CREATED') ? taskEvents$ : processEvents$).asObservable() + ); + postSpy = spyOn(service as unknown as CountEndpoint, 'post').and.returnValue(of(countersMock)); + getTaskListFiltersSpy = spyOn(TestBed.inject(TaskFilterCloudService), 'getTaskListFilters').and.returnValue(of(taskFiltersMock)); + getProcessFiltersSpy = spyOn(TestBed.inject(ProcessFilterCloudService), 'getProcessFilters').and.returnValue(of(processFiltersMock)); + }); + + describe('getTaskFilters / getProcessFilters', () => { + it('should load the filters of every entity type', async () => { + expect(await firstValueFrom(service.getTaskFilters('mock-app'))).toEqual(taskFiltersMock); + expect(await firstValueFrom(service.getProcessFilters('mock-app'))).toEqual(processFiltersMock); + }); + + it('should load the filters of an app once for concurrent subscribers', async () => { + await firstValueFrom(combineLatest([service.getTaskFilters('mock-app'), service.getTaskFilters('mock-app')])); + await firstValueFrom(combineLatest([service.getProcessFilters('mock-app'), service.getProcessFilters('mock-app')])); + + expect(getTaskListFiltersSpy).toHaveBeenCalledTimes(1); + expect(getProcessFiltersSpy).toHaveBeenCalledTimes(1); + }); + + it('should load the filters of every app', async () => { + await firstValueFrom(service.getTaskFilters('mock-app')); + await firstValueFrom(service.getTaskFilters('other-app')); + + expect(getTaskListFiltersSpy.calls.allArgs()).toEqual([['mock-app'], ['other-app']]); + }); + + it('should share the filters with the batched count request', async () => { + const subscription = service.getTaskFilters('mock-app').subscribe(); + await taskCounters(); + subscription.unsubscribe(); + + expect(getTaskListFiltersSpy).toHaveBeenCalledTimes(1); + }); + + it('should propagate the error of the filters that fail to load', async () => { + getTaskListFiltersSpy.and.returnValue(throwError(() => new Error('filters failed'))); + + await expectAsync(firstValueFrom(service.getTaskFilters('mock-app'))).toBeRejectedWithError('filters failed'); + }); + }); + + describe('getFilterCounters', () => { + it('should return EMPTY when appName is not set', () => { + let completed = false; + service.getFilterCounters('', FilterCounterEntityType.TASK).subscribe({ complete: () => (completed = true) }); + + expect(completed).toBeTrue(); + expect(postSpy).not.toHaveBeenCalled(); + }); + + it('should resolve the counters of both entity types with a single request', async () => { + expect(await bothCounters()).toEqual([ + { counters: { 'my-tasks': 5, 'queued-tasks': 0 }, batched: true }, + { counters: { 'running-processes': 5 }, batched: true } + ]); + + expect(postSpy).toHaveBeenCalledTimes(1); + }); + + it('should call the batched count endpoint of the app', async () => { + await taskCounters(); + + expect(countUrl()).toBe('https://fake-bpm-host.com/mock-app/query/v1/count'); + }); + + it('should identify the query of every filter by the key of the filter', async () => { + await bothCounters(); + + expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']); + expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['running-processes']); + }); + + it('should send the criteria of every filter along with its request id', async () => { + await taskCounters(); + + expect(countQueries(FilterCounterEntityType.TASK)[0]).toEqual({ + requestId: 'my-tasks', + status: ['ASSIGNED'], + assignee: ['mock-user'], + sort: { field: 'createdDate', direction: 'desc', isProcessVariable: false } + }); + }); + + it('should not send the filters without a counter enabled', async () => { + await taskCounters(); + + expect(countRequestIds(FilterCounterEntityType.TASK)).not.toContain('completed-tasks'); + }); + + it('should send the query of a filter targeting every status', async () => { + getProcessFiltersSpy.and.returnValue(of([processFilter({ key: 'all-processes', status: '', showCounter: true })])); + + await processCounters(); + + expect(countRequestIds(FilterCounterEntityType.PROCESS_INSTANCE)).toEqual(['all-processes']); + }); + + it('should omit an entity type without filters with a counter enabled', async () => { + getProcessFiltersSpy.and.returnValue(of([])); + + await bothCounters(); + + expect(countRequest().PROCESS_INSTANCE).toBeUndefined(); + }); + + it('should leave out a filter the query cannot be built for', async () => { + getTaskListFiltersSpy.and.returnValue( + of([taskFilter({ key: 'broken', status: 'ASSIGNED', showCounter: true, sort: undefined, order: undefined }), taskFiltersMock[1]]) + ); + + await taskCounters(); + + expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['queued-tasks']); + }); + + it('should leave out a filter without a key, since it holds no request id', async () => { + getProcessFiltersSpy.and.returnValue(of([processFilter({ key: null, status: 'RUNNING', showCounter: true })])); + + expect(await processCounters()).toEqual({ counters: {}, batched: true }); + expect(postSpy).not.toHaveBeenCalled(); + }); + + it('should resolve the counters of an entity type when the filters of the other one fail to load', async () => { + getProcessFiltersSpy.and.returnValue(throwError(() => new Error('filters failed'))); + + await bothCounters(); + + expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']); + expect(countRequest().PROCESS_INSTANCE).toBeUndefined(); + }); + + it('should resolve no counter when no filter has a counter enabled', async () => { + getTaskListFiltersSpy.and.returnValue(of([])); + getProcessFiltersSpy.and.returnValue(of([])); + + expect(await taskCounters()).toEqual({ counters: {}, batched: true }); + expect(postSpy).not.toHaveBeenCalled(); + }); + + describe('when the batched count endpoint is not available', () => { + it('should report the counters as not batched', async () => { + postSpy.and.returnValue(throwError(() => ({ status: 404 }))); + + expect(await taskCounters()).toEqual({ counters: {}, batched: false }); + }); + + it('should not call the endpoint again for the same app', async () => { + postSpy.and.returnValue(throwError(() => ({ status: 404 }))); + + await taskCounters(); + expect(await processCounters()).toEqual({ counters: {}, batched: false }); + + expect(postSpy).toHaveBeenCalledTimes(1); + }); + + it('should keep calling the endpoint of the apps that do hold it', async () => { + postSpy.and.returnValue(throwError(() => ({ status: 404 }))); + await taskCounters(); + + postSpy.and.returnValue(of(countersMock)); + expect(await taskCounters('other-app')).toEqual({ counters: { 'my-tasks': 5, 'queued-tasks': 0 }, batched: true }); + }); + + it('should keep calling the endpoint after a transient failure', async () => { + postSpy.and.returnValue(throwError(() => ({ status: 500 }))); + expect(await taskCounters()).toEqual({ counters: {}, batched: false }); + + postSpy.and.returnValue(of(countersMock)); + expect(await taskCounters()).toEqual({ counters: { 'my-tasks': 5, 'queued-tasks': 0 }, batched: true }); + expect(postSpy).toHaveBeenCalledTimes(2); + }); + }); + }); + + describe('batched counters opted in by the filter components', () => { + it('should not call the batched count endpoint when it was not asked for', async () => { + const result = await firstValueFrom(service.getFilterCounters('mock-app', FilterCounterEntityType.TASK)); + + expect(result).toEqual({ counters: {}, batched: false }); + expect(postSpy).not.toHaveBeenCalled(); + }); + + it('should not load the filters when the batched count endpoint was not asked for', async () => { + await firstValueFrom(service.getFilterCounters('mock-app', FilterCounterEntityType.TASK)); + + expect(getTaskListFiltersSpy).not.toHaveBeenCalled(); + }); + + it('should call the batched count endpoint when every entity type on screen asked for it', async () => { + await bothCounters(); + + expect(postSpy).toHaveBeenCalledTimes(1); + }); + + it('should not call the batched count endpoint when one entity type on screen did not ask for it', fakeAsync(() => { + const results: FilterCountersResult[] = []; + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe((result) => results.push(result)); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, false).subscribe(); + tick(0); + + expect(postSpy).not.toHaveBeenCalled(); + expect(results).toEqual([{ counters: {}, batched: false }]); + })); + + it('should call the batched count endpoint once the entity type that opted out leaves the screen', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + const processSubscription = service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, false).subscribe(); + tick(0); + + processSubscription.unsubscribe(); + service.refreshFilterCounters('mock-app'); + tick(0); + + expect(postSpy).toHaveBeenCalledTimes(1); + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.TASK]); + })); + }); + + describe('counters scoped to the entity types on screen', () => { + it('should send the queries of the entity type on screen alone', async () => { + await taskCounters(); + + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.TASK]); + }); + + it('should not load the filters of an entity type that is not on screen', async () => { + await taskCounters(); + + expect(getTaskListFiltersSpy).toHaveBeenCalled(); + expect(getProcessFiltersSpy).not.toHaveBeenCalled(); + }); + + it('should send the queries of both entity types when both are on screen', async () => { + await bothCounters(); + + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.TASK, FilterCounterEntityType.PROCESS_INSTANCE]); + }); + + it('should resolve the counters again when an entity type joins the ones on screen', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.TASK]); + + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + + expect(postSpy).toHaveBeenCalledTimes(2); + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.TASK, FilterCounterEntityType.PROCESS_INSTANCE]); + })); + + it('should stop covering an entity type once its counters hold no subscriber', fakeAsync(() => { + const taskSubscription = service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + + taskSubscription.unsubscribe(); + service.refreshFilterCounters('mock-app'); + tick(0); + + expect(Object.keys(countRequest())).toEqual([FilterCounterEntityType.PROCESS_INSTANCE]); + })); + }); + + describe('teardown', () => { + it('should close the engine event subscription once the counters hold no subscriber', fakeAsync(() => { + const subscription = service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(1); + + subscription.unsubscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(2); + })); + + it('should keep the engine event subscription while another subscriber holds the same entity type', fakeAsync(() => { + const subscription = service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + + subscription.unsubscribe(); + emitTaskEvent(); + tick(3000); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(1); + expect(postSpy).toHaveBeenCalledTimes(2); + })); + + it('should release the filters subscription once nothing reads them', fakeAsync(() => { + const filters$ = new BehaviorSubject(taskFiltersMock); + getTaskListFiltersSpy.and.returnValue(filters$.asObservable()); + + const subscriptions = [ + service.getTaskFilters('mock-app').subscribe(), + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe() + ]; + tick(0); + expect(filters$.observed).toBeTrue(); + + subscriptions.forEach((subscription) => subscription.unsubscribe()); + + expect(filters$.observed).toBeFalse(); + })); + + it('should resolve the counters again for a subscriber that comes after a full teardown', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe().unsubscribe(); + tick(0); + postSpy.calls.reset(); + + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + + expect(postSpy).toHaveBeenCalledTimes(1); + })); + }); + + describe('refreshFilterCounters', () => { + it('should resolve the counters again with a single request', fakeAsync(() => { + const results: FilterCountersResult[] = []; + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe((result) => results.push(result)); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + + service.refreshFilterCounters('mock-app'); + tick(0); + + expect(postSpy).toHaveBeenCalledTimes(2); + expect(results.length).toBe(2); + })); + + it('should not resolve the counters of an app without subscribers', fakeAsync(() => { + service.refreshFilterCounters('mock-app'); + tick(0); + + expect(postSpy).not.toHaveBeenCalled(); + })); + }); + + describe('when only one of the two filter families is wired', () => { + const configureTasksOnly = () => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [NoopAuthModule, ApolloTestingModule], + providers: [{ provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }] + }); + + const tasksOnlyService = TestBed.inject(FilterCountersCloudService); + TestBed.inject(AppConfigService).config.bpmHost = 'https://fake-bpm-host.com'; + spyOn(TestBed.inject(NotificationCloudService), 'makeGQLQuery').and.returnValue(new Subject().asObservable()); + spyOn(TestBed.inject(TaskFilterCloudService), 'getTaskListFilters').and.returnValue(of(taskFiltersMock)); + postSpy = spyOn(tasksOnlyService as unknown as CountEndpoint, 'post').and.returnValue(of(countersMock)); + + return tasksOnlyService; + }; + + it('should resolve the counters of the wired family', async () => { + const tasksOnlyService = configureTasksOnly(); + + const result = await firstValueFrom(tasksOnlyService.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true)); + + expect(result).toEqual({ counters: { 'my-tasks': 5, 'queued-tasks': 0 }, batched: true }); + }); + + it('should leave the filters of the family that is not wired out of the request', async () => { + const tasksOnlyService = configureTasksOnly(); + + await firstValueFrom(tasksOnlyService.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true)); + + expect(countRequestIds(FilterCounterEntityType.TASK)).toEqual(['my-tasks', 'queued-tasks']); + expect(countRequest().PROCESS_INSTANCE).toBeUndefined(); + }); + }); + + describe('getEngineEvents', () => { + it('should return EMPTY when appName is not set', () => { + let completed = false; + service.getEngineEvents('', FilterCounterEntityType.TASK).subscribe({ complete: () => (completed = true) }); + + expect(completed).toBeTrue(); + expect(makeGQLQuerySpy).not.toHaveBeenCalled(); + }); + + it('should subscribe to the events of the task entity type alone', () => { + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(); + + const [appName, query] = makeGQLQuerySpy.calls.mostRecent().args; + expect(appName).toBe('mock-app'); + expect(query).toContain('TASK_CREATED'); + expect(query).not.toContain('PROCESS_STARTED'); + }); + + it('should subscribe to the events of the process entity type alone', () => { + service.getEngineEvents('mock-app', FilterCounterEntityType.PROCESS_INSTANCE).subscribe(); + + const [, query] = makeGQLQuerySpy.calls.mostRecent().args; + expect(query).toContain('PROCESS_STARTED'); + expect(query).not.toContain('TASK_CREATED'); + }); + + it('should open a single subscription for multiple subscribers of the same entity type', () => { + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(); + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(1); + }); + + it('should open a separate subscription per entity type', () => { + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(); + service.getEngineEvents('mock-app', FilterCounterEntityType.PROCESS_INSTANCE).subscribe(); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(2); + }); + + it('should open a separate subscription per app', () => { + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(); + service.getEngineEvents('other-app', FilterCounterEntityType.TASK).subscribe(); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(2); + }); + + it('should emit the debounced batch of events', fakeAsync(() => { + const batches: TaskCloudEngineEvent[][] = []; + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe((events) => batches.push(events)); + + emitTaskEvent('TASK_CREATED'); + emitTaskEvent('TASK_ASSIGNED'); + tick(3000); + + expect(batches.length).toBe(1); + expect(batches[0][0].eventType).toBe('TASK_ASSIGNED'); + })); + + it('should debounce the events using the configured debounce time', fakeAsync(() => { + spyOnProperty(service, 'notificationDebounceTime', 'get').and.returnValue(5000); + let emitted = false; + service.getEngineEvents('mock-app', FilterCounterEntityType.TASK).subscribe(() => (emitted = true)); + + emitTaskEvent(); + tick(3000); + expect(emitted).toBeFalse(); + + tick(2000); + expect(emitted).toBeTrue(); + })); + }); + + describe('counters driven by the engine events', () => { + it('should make a single count request for a batch of events of both entity types', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + postSpy.calls.reset(); + + emitTaskEvent(); + emitProcessEvent(); + tick(3000); + + expect(postSpy).toHaveBeenCalledTimes(1); + })); + + it('should make a single count request for the events of both entity types arriving apart', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + postSpy.calls.reset(); + + emitTaskEvent(); + tick(1000); + emitProcessEvent(); + tick(3000); + + expect(postSpy).toHaveBeenCalledTimes(1); + })); + + it('should emit the counters resolved for the batch of events', fakeAsync(() => { + const results: FilterCountersResult[] = []; + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe((result) => results.push(result)); + tick(0); + + postSpy.and.returnValue(of({ TASK: { 'my-tasks': 9 } })); + emitTaskEvent(); + tick(3000); + + expect(results.length).toBe(2); + expect(results[1]).toEqual({ counters: { 'my-tasks': 9 }, batched: true }); + })); + + it('should not subscribe to the events of an entity type that is not on screen', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + + expect(makeGQLQuerySpy).toHaveBeenCalledTimes(1); + expect(makeGQLQuerySpy.calls.mostRecent().args[1]).toContain('TASK_CREATED'); + })); + + it('should not resolve the counters again on the events of an entity type that is not on screen', fakeAsync(() => { + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(0); + postSpy.calls.reset(); + + emitProcessEvent(); + tick(3000); + + expect(postSpy).not.toHaveBeenCalled(); + })); + + it('should stop resolving the counters on the events of an entity type that left the screen', fakeAsync(() => { + const taskSubscription = service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + service.getFilterCounters('mock-app', FilterCounterEntityType.PROCESS_INSTANCE, true).subscribe(); + tick(0); + + taskSubscription.unsubscribe(); + postSpy.calls.reset(); + emitTaskEvent(); + tick(3000); + + expect(postSpy).not.toHaveBeenCalled(); + })); + + it('should not subscribe to the engine events when notifications are disabled', fakeAsync(() => { + appConfigService.config.notifications = false; + + service.getFilterCounters('mock-app', FilterCounterEntityType.TASK, true).subscribe(); + tick(3000); + + expect(makeGQLQuerySpy).not.toHaveBeenCalled(); + expect(postSpy).toHaveBeenCalledTimes(1); + })); + }); +}); diff --git a/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts new file mode 100644 index 0000000000..0d0efb20dc --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/filter-counters-cloud.service.ts @@ -0,0 +1,375 @@ +/*! + * @license + * Copyright © 2005-2026 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 { inject, Injectable, Injector } from '@angular/core'; +import { asapScheduler, combineLatest, defer, EMPTY, merge, Observable, of, Subject, Subscription } from 'rxjs'; +import { catchError, debounceTime, finalize, map, shareReplay, switchMap, take } from 'rxjs/operators'; +import { BaseCloudService } from './base-cloud.service'; +import { NotificationCloudService } from './notification-cloud.service'; +import { TaskCloudEngineEvent } from '../models/engine-event-cloud.model'; +import { TaskFilterCloudService } from '../task/task-filters/services/task-filter-cloud.service'; +import { ProcessFilterCloudService } from '../process/process-filters/services/process-filter-cloud.service'; +import { TaskListCloudService } from '../task/task-list/services/task-list-cloud.service'; +import { ProcessListCloudService } from '../process/process-list/services/process-list-cloud.service'; +import { TaskFilterCloudAdapter } from '../models/filter-cloud-model'; +import { TaskFilterCloudModel } from '../task/task-filters/models/filter-cloud.model'; +import { ProcessFilterCloudModel } from '../process/process-filters/models/process-filter-cloud.model'; +import { ProcessFilterCloudAdapter } from '../process/process-list/models/process-cloud-query-request.model'; +import { + FilterCounterCandidate, + FilterCounterEntityType, + FilterCounters, + FilterCountersQuery, + FilterCountersRequest, + FilterCountersResult +} from '../models/filter-counters-cloud.model'; +const BATCHED_COUNTERS_UNAVAILABLE_STATUSES = [404, 501]; + +interface FilterCountersFilters { + [FilterCounterEntityType.TASK]: TaskFilterCloudModel[]; + [FilterCounterEntityType.PROCESS_INSTANCE]: ProcessFilterCloudModel[]; +} + +interface EngineEventsData { + engineEvents?: TaskCloudEngineEvent[]; +} + +const ENGINE_EVENTS_SUBSCRIPTION_QUERIES: Record = { + [FilterCounterEntityType.TASK]: ` + subscription { + engineEvents(eventType: [ + TASK_COMPLETED + TASK_ASSIGNED + TASK_ACTIVATED + TASK_SUSPENDED + TASK_CANCELLED + TASK_CREATED + ]) { + eventType + entity + } + } +`, + [FilterCounterEntityType.PROCESS_INSTANCE]: ` + subscription { + engineEvents(eventType: [ + PROCESS_CANCELLED + PROCESS_COMPLETED + PROCESS_CREATED + PROCESS_RESUMED + PROCESS_SUSPENDED + PROCESS_STARTED + ]) { + eventType + entity + } + } +` +}; + +@Injectable({ providedIn: 'root' }) +export class FilterCountersCloudService extends BaseCloudService { + private readonly notificationCloudService = inject(NotificationCloudService); + private readonly taskListCloudService = inject(TaskListCloudService); + private readonly processListCloudService = inject(ProcessListCloudService); + private readonly injector = inject(Injector); + + private readonly eventsPerEntityType = new Map>(); + private readonly rawEventsPerEntityType = new Map>(); + private readonly recountPerApp = new Map>(); + private readonly eventRecountPerApp = new Map>(); + private readonly activeEntityTypesPerApp = new Map>(); + private readonly subscribersPerEntityType = new Map(); + private readonly batchedCountersPerEntityType = new Map(); + private readonly eventSubscriptionsPerEntityType = new Map(); + private readonly appsWithoutBatchedCounters = new Set(); + private readonly taskFiltersPerApp = new Map>(); + private readonly processFiltersPerApp = new Map>(); + private readonly countersPerApp = new Map>(); + + get notificationDebounceTime(): number { + return this.appConfigService.get('notificationDebounceTime', 3000); + } + + getTaskFilters(appName: string): Observable { + return this.shareFilters(this.taskFiltersPerApp, appName, () => this.injector.get(TaskFilterCloudService).getTaskListFilters(appName)); + } + + getProcessFilters(appName: string): Observable { + return this.shareFilters(this.processFiltersPerApp, appName, () => this.injector.get(ProcessFilterCloudService).getProcessFilters(appName)); + } + + getFilterCounters(appName: string, entityType: FilterCounterEntityType, batchedCounters = false): Observable { + if (!appName) { + return EMPTY; + } + + return defer(() => { + this.activateEntityType(appName, entityType, batchedCounters); + + return this.getCounters(appName); + }).pipe( + map(({ counters, batched }) => ({ counters: counters[entityType] ?? {}, batched })), + finalize(() => this.deactivateEntityType(appName, entityType)) + ); + } + + refreshFilterCounters(appName: string): void { + this.recount(appName); + } + + getEngineEvents(appName: string, entityType: FilterCounterEntityType): Observable { + if (!appName) { + return EMPTY; + } + + const key = this.entityTypeKey(appName, entityType); + let events$ = this.eventsPerEntityType.get(key); + if (!events$) { + events$ = this.rawEngineEvents(appName, entityType).pipe( + debounceTime(this.notificationDebounceTime), + shareReplay({ bufferSize: 1, refCount: true }) + ); + this.eventsPerEntityType.set(key, events$); + } + + return events$; + } + + private rawEngineEvents(appName: string, entityType: FilterCounterEntityType): Observable { + const key = this.entityTypeKey(appName, entityType); + let events$ = this.rawEventsPerEntityType.get(key); + if (!events$) { + events$ = defer(() => + this.notificationCloudService.makeGQLQuery(appName, ENGINE_EVENTS_SUBSCRIPTION_QUERIES[entityType]) + ).pipe( + map((result) => result.data?.engineEvents ?? []), + catchError(() => EMPTY), + shareReplay({ bufferSize: 1, refCount: true }) + ); + this.rawEventsPerEntityType.set(key, events$); + } + + return events$; + } + + private get notificationsEnabled(): boolean { + return this.appConfigService.get('notifications', true); + } + + private activateEntityType(appName: string, entityType: FilterCounterEntityType, batchedCounters: boolean): void { + const key = this.entityTypeKey(appName, entityType); + const subscribers = (this.subscribersPerEntityType.get(key) ?? 0) + 1; + this.subscribersPerEntityType.set(key, subscribers); + + if (subscribers > 1) { + return; + } + + this.batchedCountersPerEntityType.set(key, batchedCounters); + + const activeEntityTypes = this.activeEntityTypes(appName); + const joinsResolvedCounters = activeEntityTypes.size > 0; + activeEntityTypes.add(entityType); + + if (this.notificationsEnabled) { + this.eventSubscriptionsPerEntityType.set( + key, + this.rawEngineEvents(appName, entityType).subscribe(() => this.eventRecountTrigger(appName).next()) + ); + } + + if (joinsResolvedCounters) { + this.recount(appName); + } + } + + private deactivateEntityType(appName: string, entityType: FilterCounterEntityType): void { + const key = this.entityTypeKey(appName, entityType); + const subscribers = (this.subscribersPerEntityType.get(key) ?? 1) - 1; + + if (subscribers > 0) { + this.subscribersPerEntityType.set(key, subscribers); + return; + } + + this.subscribersPerEntityType.delete(key); + this.batchedCountersPerEntityType.delete(key); + this.activeEntityTypes(appName).delete(entityType); + this.eventSubscriptionsPerEntityType.get(key)?.unsubscribe(); + this.eventSubscriptionsPerEntityType.delete(key); + } + + private activeEntityTypes(appName: string): Set { + let activeEntityTypes = this.activeEntityTypesPerApp.get(appName); + if (!activeEntityTypes) { + activeEntityTypes = new Set(); + this.activeEntityTypesPerApp.set(appName, activeEntityTypes); + } + + return activeEntityTypes; + } + + private entityTypeKey(appName: string, entityType: FilterCounterEntityType): string { + return `${appName}|${entityType}`; + } + + private recount(appName: string): void { + this.recountTrigger(appName).next(); + } + + private getFiltersForCounters(appName: string): Observable { + const activeEntityTypes = this.activeEntityTypes(appName); + + return combineLatest({ + [FilterCounterEntityType.TASK]: activeEntityTypes.has(FilterCounterEntityType.TASK) + ? this.getTaskFilters(appName).pipe(catchError(() => of([]))) + : of([]), + [FilterCounterEntityType.PROCESS_INSTANCE]: activeEntityTypes.has(FilterCounterEntityType.PROCESS_INSTANCE) + ? this.getProcessFilters(appName).pipe(catchError(() => of([]))) + : of([]) + }); + } + + private shareFilters(cache: Map>, appName: string, loadFilters: () => Observable): Observable { + let filters$ = cache.get(appName); + if (!filters$) { + filters$ = defer(loadFilters).pipe(shareReplay({ bufferSize: 1, refCount: true })); + cache.set(appName, filters$); + } + + return filters$; + } + + private getCounters(appName: string): Observable<{ counters: FilterCounters; batched: boolean }> { + let counters$ = this.countersPerApp.get(appName); + if (!counters$) { + counters$ = this.recounts(appName).pipe( + switchMap(() => this.resolveCounters(appName)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + this.countersPerApp.set(appName, counters$); + } + + return counters$; + } + + private resolveCounters(appName: string): Observable<{ counters: FilterCounters; batched: boolean }> { + if (!this.batchedCountersEnabled(appName) || this.appsWithoutBatchedCounters.has(appName)) { + return of({ counters: {}, batched: false }); + } + + return this.getFiltersForCounters(appName).pipe( + take(1), + switchMap((filters) => this.fetchFilterCounters(appName, this.buildRequest(filters))), + map((counters) => ({ counters, batched: true })), + catchError((error) => { + if (BATCHED_COUNTERS_UNAVAILABLE_STATUSES.includes(error?.status)) { + this.appsWithoutBatchedCounters.add(appName); + } + + return of({ counters: {}, batched: false }); + }) + ); + } + + private batchedCountersEnabled(appName: string): boolean { + const activeEntityTypes = [...this.activeEntityTypes(appName)]; + + return ( + activeEntityTypes.length > 0 && + activeEntityTypes.every((entityType) => this.batchedCountersPerEntityType.get(this.entityTypeKey(appName, entityType))) + ); + } + + private recounts(appName: string): Observable { + return merge( + merge(of(undefined), this.recountTrigger(appName)).pipe(debounceTime(0, asapScheduler)), + this.eventRecountTrigger(appName).pipe(debounceTime(this.notificationDebounceTime)) + ); + } + + private recountTrigger(appName: string): Subject { + let recount$ = this.recountPerApp.get(appName); + if (!recount$) { + recount$ = new Subject(); + this.recountPerApp.set(appName, recount$); + } + + return recount$; + } + + private eventRecountTrigger(appName: string): Subject { + let eventRecount$ = this.eventRecountPerApp.get(appName); + if (!eventRecount$) { + eventRecount$ = new Subject(); + this.eventRecountPerApp.set(appName, eventRecount$); + } + + return eventRecount$; + } + + private buildRequest(filters: FilterCountersFilters): FilterCountersRequest { + const request: FilterCountersRequest = {}; + + const taskQueries = this.buildQueries(filters[FilterCounterEntityType.TASK], (filter) => + this.taskListCloudService.buildQueryData(new TaskFilterCloudAdapter(filter)) + ); + if (taskQueries.length) { + request[FilterCounterEntityType.TASK] = taskQueries; + } + + const processQueries = this.buildQueries(filters[FilterCounterEntityType.PROCESS_INSTANCE], (filter) => + this.processListCloudService.buildQueryData(new ProcessFilterCloudAdapter(filter)) + ); + if (processQueries.length) { + request[FilterCounterEntityType.PROCESS_INSTANCE] = processQueries; + } + + return request; + } + + private buildQueries( + filters: T[], + buildQuery: (filter: T) => Omit + ): FilterCountersQuery[] { + return (filters ?? []) + .filter((filter) => filter?.showCounter && this.isCounterBatched(filter)) + .map((filter) => { + try { + return { ...buildQuery(filter), requestId: filter.key as string }; + } catch { + return undefined; + } + }) + .filter((query): query is FilterCountersQuery => !!query); + } + + private fetchFilterCounters(appName: string, request: FilterCountersRequest): Observable { + if (!Object.keys(request).length) { + return of({}); + } + + const queryUrl = `${this.getBasePath(appName)}/query/v1/count`; + + return this.post(queryUrl, request).pipe(map((counters) => counters || {})); + } + + private isCounterBatched(filter: FilterCounterCandidate): boolean { + return !!filter?.key; + } +} diff --git a/lib/process-services-cloud/src/lib/services/notification-cloud.service.ts b/lib/process-services-cloud/src/lib/services/notification-cloud.service.ts index ae0cfe68fc..7b5a46571a 100644 --- a/lib/process-services-cloud/src/lib/services/notification-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/services/notification-cloud.service.ts @@ -15,8 +15,9 @@ * limitations under the License. */ -import { gql } from '@apollo/client/core'; +import { FetchResult, gql } from '@apollo/client/core'; import { Injectable, inject } from '@angular/core'; +import { Observable } from 'rxjs'; import { WebSocketService } from './web-socket.service'; @Injectable({ providedIn: 'root' @@ -24,8 +25,8 @@ import { WebSocketService } from './web-socket.service'; export class NotificationCloudService { private readonly webSocketService = inject(WebSocketService); - makeGQLQuery(appName: string, gqlQuery: string) { - return this.webSocketService.getSubscription({ + makeGQLQuery(appName: string, gqlQuery: string): Observable> { + return this.webSocketService.getSubscription({ apolloClientName: appName, wsUrl: `${appName}/notifications`, httpUrl: `${appName}/notifications/v2/ws/graphql`, diff --git a/lib/process-services-cloud/src/lib/services/public-api.ts b/lib/process-services-cloud/src/lib/services/public-api.ts index e3ab7c3b05..e300e35a83 100644 --- a/lib/process-services-cloud/src/lib/services/public-api.ts +++ b/lib/process-services-cloud/src/lib/services/public-api.ts @@ -17,6 +17,7 @@ export * from './base-cloud.service'; export * from './cloud-token.service'; +export * from './filter-counters-cloud.service'; export * from './form-fields.interfaces'; export * from './local-preference-cloud.service'; export * from './notification-cloud.service'; diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts index 5fb1f21daf..6fda86d7a0 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.spec.ts @@ -17,10 +17,10 @@ import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core'; import { Component, SimpleChange } from '@angular/core'; -import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { first, of, Subject, throwError } from 'rxjs'; -import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; +import { first, NEVER, of, Subject, throwError } from 'rxjs'; +import { PROCESS_FILTERS_SERVICE_TOKEN, TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service'; import { defaultTaskFiltersMock, fakeGlobalFilter, taskNotifications } from '../../mock/task-filters-cloud.mock'; import { TaskFilterCloudService } from '../../services/task-filter-cloud.service'; @@ -35,6 +35,9 @@ import { TaskFilterCloudModel } from '../../models/filter-cloud.model'; import { MatIconHarness } from '@angular/material/icon/testing'; import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { RouterTestingHarness } from '@angular/router/testing'; +import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service'; +import { FilterCounterEntityType } from '../../../../models/filter-counters-cloud.model'; +import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model'; @Component({ selector: 'adf-cloud-dummy', template: '' }) class DummyComponent {} @@ -50,7 +53,10 @@ describe('TaskFiltersCloudComponent', () => { let getTaskFilterCounterSpy: jasmine.Spy; let getTaskListFiltersSpy: jasmine.Spy; let getTaskListCountSpy: jasmine.Spy; - let getTaskNotificationSubscriptionSpy: jasmine.Spy; + let getEngineEventsSpy: jasmine.Spy; + let filterCountersService: FilterCountersCloudService; + let getFilterCountersSpy: jasmine.Spy; + let refreshFilterCountersSpy: jasmine.Spy; let router: Router; const configureTestingModule = async (searchApiMethod: 'GET' | 'POST') => { @@ -58,6 +64,7 @@ describe('TaskFiltersCloudComponent', () => { imports: [NoopAuthModule, TaskFiltersCloudComponent, ApolloTestingModule], providers: [ { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, + { provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService }, provideRouter([{ path: 'task-list-cloud', component: DummyComponent }]), { provide: ActivatedRoute, @@ -76,10 +83,15 @@ describe('TaskFiltersCloudComponent', () => { }); taskFilterService = TestBed.inject(TaskFilterCloudService); taskListService = TestBed.inject(TaskListCloudService); + filterCountersService = TestBed.inject(FilterCountersCloudService); getTaskFilterCounterSpy = spyOn(taskFilterService, 'getTaskFilterCounter').and.returnValue(of(11)); getTaskListCountSpy = spyOn(taskListService, 'getTaskListCount').and.returnValue(of(11)); - getTaskNotificationSubscriptionSpy = spyOn(taskFilterService, 'getTaskNotificationSubscription').and.returnValue(of(taskNotifications)); - getTaskListFiltersSpy = spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeGlobalFilter)); + getEngineEventsSpy = spyOn(filterCountersService, 'getEngineEvents').and.returnValue(of(taskNotifications)); + getTaskListFiltersSpy = spyOn(filterCountersService, 'getTaskFilters').and.returnValue(of(fakeGlobalFilter)); + getFilterCountersSpy = spyOn(filterCountersService, 'getFilterCounters').and.returnValue( + of({ counters: { 'fake-involved-tasks': 11 }, batched: true }) + ); + refreshFilterCountersSpy = spyOn(filterCountersService, 'refreshFilterCounters'); appConfigService = TestBed.inject(AppConfigService); @@ -261,7 +273,7 @@ describe('TaskFiltersCloudComponent', () => { expect(updatedFilterCounters.length).toBe(0); }); - it('should update filter counter when filter is selected', async () => { + it('should refresh the filter counters when a filter is selected', async () => { component.showIcons = true; await bindAppName(); @@ -269,7 +281,7 @@ describe('TaskFiltersCloudComponent', () => { filterButton.click(); fixture.detectChanges(); - expect(getTaskFilterCounterSpy).toHaveBeenCalledWith(fakeGlobalFilter[0]); + expect(refreshFilterCountersSpy).toHaveBeenCalledWith('my-app-1'); }); describe('Notifications config', () => { @@ -306,30 +318,33 @@ describe('TaskFiltersCloudComponent', () => { }); it('should not subscribe to notifications when appName is missing', () => { - getTaskNotificationSubscriptionSpy.calls.reset(); + getEngineEventsSpy.calls.reset(); component.appName = ''; fixture.detectChanges(); - expect(getTaskNotificationSubscriptionSpy).not.toHaveBeenCalled(); + expect(getEngineEventsSpy).not.toHaveBeenCalled(); }); - it('should debounce notification subscription using the configured debounce time', fakeAsync(() => { - const notifications$ = new Subject(); - getTaskNotificationSubscriptionSpy.and.returnValue(notifications$.asObservable()); + it('should subscribe to the notifications of the bound app', () => { component.appName = 'my-app-1'; fixture.detectChanges(); - const updateFilterCountersSpy = spyOn(component, 'updateFilterCounters'); + expect(getEngineEventsSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK); + }); - notifications$.next(taskNotifications); - tick(1000); - expect(updateFilterCountersSpy).not.toHaveBeenCalled(); + it('should emit the events of the debounced batch', fakeAsync(() => { + const events$ = new Subject(); + getEngineEventsSpy.and.returnValue(events$.asObservable()); + const filterCounterUpdatedSpy = spyOn(component.filterCounterUpdated, 'emit'); + component.appName = 'my-app-1'; - tick(2000); - expect(updateFilterCountersSpy).toHaveBeenCalledTimes(1); + fixture.detectChanges(); + events$.next(taskNotifications); + + expect(filterCounterUpdatedSpy).toHaveBeenCalledWith(taskNotifications); flush(); })); }); @@ -438,7 +453,7 @@ describe('TaskFiltersCloudComponent', () => { expect(updatedFilterCounters.length).toBe(0); }); - it('should update filter counter when filter is selected', async () => { + it('should refresh the filter counters when a filter is selected', async () => { await bindAppName(); const filterButton = await loader.getHarness( @@ -446,6 +461,14 @@ describe('TaskFiltersCloudComponent', () => { ); await filterButton.click(); + expect(refreshFilterCountersSpy).toHaveBeenCalledWith('my-app-1'); + }); + + it('should resolve the counters with the POST method when the batched endpoint is not available', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); + + await bindAppName(); + expect(getTaskListCountSpy).toHaveBeenCalledWith(new TaskFilterCloudAdapter(fakeGlobalFilter[0])); }); }); @@ -658,17 +681,137 @@ describe('TaskFiltersCloudComponent', () => { expect(component.updatedCountersSet.has(fakeFilterKey)).toBe(true); }); - it('should call fetchTaskFilterCounter only if filter.showCounter is true', () => { + it('should resolve the counter only of the filters with a counter enabled', () => { const filterWithCounter = new TaskFilterCloudModel({ ...defaultTaskFiltersMock[0], showCounter: true }); const filterWithoutCounter = new TaskFilterCloudModel({ ...defaultTaskFiltersMock[1], showCounter: false }); - const fetchSpy = spyOn(component, 'fetchTaskFilterCounter').and.returnValue(of(42)); + getTaskFilterCounterSpy.calls.reset(); component.filters = [filterWithCounter, filterWithoutCounter]; component.updateFilterCounters(); - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(filterWithCounter); - expect(fetchSpy).not.toHaveBeenCalledWith(filterWithoutCounter); + expect(getTaskFilterCounterSpy).toHaveBeenCalledTimes(1); + expect(getTaskFilterCounterSpy).toHaveBeenCalledWith(filterWithCounter); + }); + + describe('Batched counters', () => { + it('should read the counters without waiting for the filters', async () => { + getTaskListFiltersSpy.and.returnValue(NEVER); + + await bindAppName(); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK, false); + }); + + it('should hold the counters until the filters they belong to arrive', async () => { + const filters$ = new Subject(); + getTaskListFiltersSpy.and.returnValue(filters$.asObservable()); + getFilterCountersSpy.and.returnValue(of({ counters: { 'fake-involved-tasks': 9 }, batched: true })); + + await bindAppName(); + expect(component.counters['fake-involved-tasks']).toBeUndefined(); + + filters$.next(fakeGlobalFilter); + fixture.detectChanges(); + + expect(component.counters['fake-involved-tasks']).toBe(9); + }); + + it('should read the counters of the task filters of the bound app', async () => { + await bindAppName(); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK, false); + }); + + it('should not ask for the batched count endpoint by default', async () => { + await bindAppName(); + + expect(component.useBatchedCounters).toBeFalse(); + expect(getFilterCountersSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK, false); + }); + + it('should ask for the batched count endpoint when the input is set', async () => { + fixture.componentRef.setInput('useBatchedCounters', true); + + await bindAppName(); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK, true); + }); + + it('should read the counters again when the input changes', async () => { + await bindAppName(); + getFilterCountersSpy.calls.reset(); + + fixture.componentRef.setInput('useBatchedCounters', true); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(getFilterCountersSpy).toHaveBeenCalledWith('my-app-1', FilterCounterEntityType.TASK, true); + }); + + it('should hold the counters resolved by the batched count request', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { 'fake-involved-tasks': 9 }, batched: true })); + + await bindAppName(); + + expect(component.counters['fake-involved-tasks']).toBe(9); + }); + + it('should emit the filters whose counter changed', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: { 'fake-involved-tasks': 9 }, batched: true })); + const updatedFilterSpy = spyOn(component.updatedFilter, 'emit'); + + await bindAppName(); + + expect(updatedFilterSpy).toHaveBeenCalledWith('fake-involved-tasks'); + }); + + it('should resolve the counter of a filter the batch left out on its own', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: true })); + + await bindAppName(); + + expect(getTaskFilterCounterSpy).toHaveBeenCalledWith(fakeGlobalFilter[0]); + expect(component.counters['fake-involved-tasks']).toBe(11); + }); + + it('should keep the counters of the other filters when one counter cannot be resolved', async () => { + getTaskListFiltersSpy.and.returnValue(of([fakeGlobalFilter[0], { ...fakeGlobalFilter[1], showCounter: true }])); + getFilterCountersSpy.and.returnValue(of({ counters: { 'fake-involved-tasks': 4 }, batched: true })); + getTaskFilterCounterSpy.and.throwError('the query of the filter cannot be built'); + + await bindAppName(); + + expect(component.counters['fake-involved-tasks']).toBe(4); + expect(component.counters['fake-my-task1']).toBe(0); + }); + + it('should resolve the counters one filter at a time when the batched endpoint is not available', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); + + await bindAppName(); + + expect(getTaskFilterCounterSpy).toHaveBeenCalled(); + expect(component.counters['fake-involved-tasks']).toBe(11); + }); + + it('should refresh the counters of every filter when a filter is clicked', async () => { + await bindAppName(); + + component.onFilterClick(fakeGlobalFilter[0]); + + expect(refreshFilterCountersSpy).toHaveBeenCalledWith('my-app-1'); + }); + + it('should refresh the counter of the clicked filter alone when the batched endpoint is not available', async () => { + getFilterCountersSpy.and.returnValue(of({ counters: {}, batched: false })); + await bindAppName(); + getTaskFilterCounterSpy.calls.reset(); + + component.onFilterClick(fakeGlobalFilter[0]); + + expect(refreshFilterCountersSpy).not.toHaveBeenCalled(); + expect(getTaskFilterCounterSpy).toHaveBeenCalledTimes(1); + }); }); describe('Highlight Selected Filter', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts index 3c553aa7c2..54f2891877 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters/task-filters-cloud.component.ts @@ -16,16 +16,18 @@ */ import { Component, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; -import { EMPTY, Observable } from 'rxjs'; +import { combineLatest, defer, EMPTY, Observable, of, Subscription } from 'rxjs'; import { TaskFilterCloudService } from '../../services/task-filter-cloud.service'; import { FilterParamsModel, TaskFilterCloudModel } from '../../models/filter-cloud.model'; import { AppConfigService, IconModule, TranslationService } from '@alfresco/adf-core'; -import { catchError, debounceTime, map, shareReplay, tap } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { BaseTaskFiltersCloudComponent } from '../base-task-filters-cloud.component'; import { TaskDetailsCloudModel } from '../../../models/task-details-cloud.model'; import { TaskCloudEngineEvent } from '../../../../models/engine-event-cloud.model'; import { TaskListCloudService } from '../../../task-list/services/task-list-cloud.service'; import { TaskFilterCloudAdapter } from '../../../../models/filter-cloud-model'; +import { FilterCountersCloudService } from '../../../../services/filter-counters-cloud.service'; +import { FilterCounterEntityType } from '../../../../models/filter-counters-cloud.model'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { TranslatePipe } from '@ngx-translate/core'; @@ -42,10 +44,21 @@ import { AsyncPipe } from '@angular/common'; export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent implements OnInit, OnChanges { protected readonly TASKS_ROUTE = '/task-list-cloud'; - /** (optional) From Activiti 8.7.0 forward, use the 'POST' method to get the task count. */ + /** + * (optional) From Activiti 8.7.0 forward, use the 'POST' method to get the task count. + * + */ @Input() searchApiMethod: 'GET' | 'POST' = 'GET'; + /** + * (optional) Resolves the counters of the task and the process filters with a single call to + * `POST /query/v1/count`. Both filter components have to + * ask for it, otherwise the counters are resolved one filter at a time. + */ + @Input() + useBatchedCounters = false; + /** Emitted when a filter is being selected based on the filterParam input. */ @Output() filterSelected = new EventEmitter(); @@ -69,9 +82,13 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp notificationDebounceTime = 3000; currentFiltersValues: { [key: string]: number } = {}; private filtersLoadedFor?: string; + private countersSubscription?: Subscription; + private countersFilters$?: Observable; + private batchedCounters = true; private readonly taskFilterCloudService = inject(TaskFilterCloudService); private readonly taskListCloudService = inject(TaskListCloudService); + private readonly filterCountersCloudService = inject(FilterCountersCloudService); private readonly translationService = inject(TranslationService); private readonly appConfigService = inject(AppConfigService); private readonly activatedRoute = inject(ActivatedRoute); @@ -80,6 +97,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp ngOnInit() { this.enableNotifications = this.appConfigService.get('notifications', true); this.notificationDebounceTime = this.appConfigService.get('notificationDebounceTime', 3000); + if (!this.filtersLoadedFor) { this.getFilters(this.appName); } @@ -94,6 +112,8 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp this.getFilters(appName.currentValue); } else if (filter && filter.currentValue !== filter.previousValue) { this.selectFilterAndEmit(filter.currentValue); + } else if (changes['useBatchedCounters'] && !changes['useBatchedCounters'].firstChange && this.filtersLoadedFor) { + this.loadFilterCounters(this.filtersLoadedFor); } } @@ -104,8 +124,8 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp */ getFilters(appName: string): void { this.filtersLoadedFor = appName; - const filters$ = this.taskFilterCloudService.getTaskListFilters(appName).pipe(shareReplay({ bufferSize: 1, refCount: true })); - this.filters$ = filters$.pipe(catchError(() => EMPTY)); + const filters$ = this.filterCountersCloudService.getTaskFilters(appName); + this.filters$ = filters$.pipe(catchError(() => of([]))); filters$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: (res) => { @@ -113,13 +133,15 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp this.filters = res || []; this.initFilterCounters(); this.selectFilterAndEmit(this.filterParam); - this.updateFilterCounters(); this.success.emit(res); }, error: (err) => { this.error.emit(err); } }); + + this.countersFilters$ = filters$; + this.loadFilterCounters(appName); } /** @@ -131,55 +153,47 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp /** * Iterate over filters and update counters + * + * @deprecated counts one filter at a time. Removed in ADF 10.0.0. */ updateFilterCounters(): void { - this.filters.forEach((filter: TaskFilterCloudModel) => this.updateFilterCounter(filter)); + this.filters.forEach((filter) => this.updateFilterCounter(filter)); } /** * Get current value for filter and check if value has changed * * @param filter filter + * @deprecated counts one filter at a time. Removed in ADF 10.0.0. */ updateFilterCounter(filter: TaskFilterCloudModel): void { if (!filter?.showCounter) { return; } - this.fetchTaskFilterCounter(filter) + + defer(() => this.fetchTaskFilterCounter(filter)) .pipe( - tap((filterCounter) => { - this.checkIfFilterValuesHasBeenUpdated(filter.key, filterCounter); - }) + catchError(() => EMPTY), + takeUntilDestroyed(this.destroyRef) ) - .subscribe((data) => { - this.counters = { - ...this.counters, - [filter.key]: data - }; + .subscribe((counter) => { + this.checkIfFilterValuesHasBeenUpdated(filter.key, counter); + this.counters = { ...this.counters, [filter.key]: counter }; }); } - private fetchTaskFilterCounter(filter: TaskFilterCloudModel): Observable { - return this.searchApiMethod === 'POST' - ? this.taskListCloudService.getTaskListCount(new TaskFilterCloudAdapter(filter)) - : this.taskFilterCloudService.getTaskFilterCounter(filter); - } - - initFilterCounterNotifications() { + initFilterCounterNotifications(): void { if (!this.appName) { return; } - if (this.enableNotifications) { - this.taskFilterCloudService - .getTaskNotificationSubscription(this.appName) - .pipe(debounceTime(this.notificationDebounceTime), takeUntilDestroyed(this.destroyRef)) - .subscribe((result) => { - result.forEach((taskEvent) => { - this.checkFilterCounter(taskEvent.entity); - }); - this.updateFilterCounters(); - this.filterCounterUpdated.emit(result); + if (this.enableNotifications) { + this.filterCountersCloudService + .getEngineEvents(this.appName, FilterCounterEntityType.TASK) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((events) => { + events.forEach((taskEvent) => this.checkFilterCounter(taskEvent.entity)); + this.filterCounterUpdated.emit(events); }); } else { this.counters = {}; @@ -240,7 +254,7 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp onFilterClick(filter: FilterParamsModel) { if (filter) { this.selectFilter(filter); - this.updateFilterCounter(this.currentFilter); + this.refreshFilterCounter(this.currentFilter); this.filterClicked.emit(this.currentFilter); this.updatedCountersSet.delete(filter.key); } else { @@ -267,17 +281,9 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp return this.filters === undefined || (this.filters && this.filters.length === 0); } - /** - * Reset the filters properties - */ - private resetFilter() { - this.filters = []; - this.currentFilter = undefined; - } - checkIfFilterValuesHasBeenUpdated(filterKey: string, filterValue: number) { if (this.currentFiltersValues[filterKey] === undefined || this.currentFiltersValues[filterKey] !== filterValue) { - this.currentFiltersValues[filterKey] = filterValue; + this.currentFiltersValues = { ...this.currentFiltersValues, [filterKey]: filterValue }; this.updatedFilter.emit(filterKey); this.updatedCountersSet.add(filterKey); } @@ -288,8 +294,69 @@ export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent imp * */ getFilterKeysAfterExternalRefreshing(): void { - this.taskFilterCloudService.filterKeyToBeRefreshed$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((filterKey: string) => { - this.updatedCountersSet.delete(filterKey); + this.taskFilterCloudService.filterKeyToBeRefreshed$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((filterKey: string) => this.updatedCountersSet.delete(filterKey)); + } + + private loadFilterCounters(appName: string): void { + if (!this.countersFilters$) { + return; + } + + this.countersSubscription?.unsubscribe(); + this.countersSubscription = combineLatest([ + this.countersFilters$.pipe(catchError(() => of([]))), + this.filterCountersCloudService.getFilterCounters(appName, FilterCounterEntityType.TASK, this.useBatchedCounters) + ]) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(([, { counters, batched }]) => { + this.batchedCounters = batched; + if (batched) { + this.applyFilterCounters(counters); + } else { + this.updateFilterCounters(); + } + }); + } + + private applyFilterCounters(counters: { [filterKey: string]: number }): void { + this.filters.forEach((filter) => { + const filterKey = filter?.showCounter ? filter.key : undefined; + if (!filterKey) { + return; + } + + const counter = counters[filterKey]; + if (counter === undefined) { + this.updateFilterCounter(filter); + return; + } + + this.checkIfFilterValuesHasBeenUpdated(filterKey, counter); + this.counters = { ...this.counters, [filterKey]: counter }; }); } + + private fetchTaskFilterCounter(filter: TaskFilterCloudModel): Observable { + return this.searchApiMethod === 'POST' + ? this.taskListCloudService.getTaskListCount(new TaskFilterCloudAdapter(filter)) + : this.taskFilterCloudService.getTaskFilterCounter(filter); + } + + /** + * Reset the filters properties + */ + private resetFilter() { + this.filters = []; + this.currentFilter = undefined; + } + + private refreshFilterCounter(filter: TaskFilterCloudModel): void { + if (this.batchedCounters) { + this.filterCountersCloudService.refreshFilterCounters(this.appName); + } else { + this.updateFilterCounter(filter); + } + } } diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts index 0018f4231f..e8a38a776b 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts @@ -361,6 +361,11 @@ export class TaskFilterCloudService extends BaseCloudService { ]; } + /** + * @deprecated use FilterCountersCloudService.getEngineEvents instead. + * @param appName Name of the target app + * @returns Task engine events + */ getTaskNotificationSubscription(appName: string): Observable { return this.notificationCloudService .makeGQLQuery(appName, TASK_EVENT_SUBSCRIPTION_QUERY) diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts index 7d6f6ce04c..c46f7f7ef0 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts @@ -135,7 +135,7 @@ export class TaskListCloudService extends BaseCloudService implements TaskListCl return this.post(queryUrl, queryData).pipe(map((response) => response || 0)); } - protected buildQueryData(requestNode: TaskListRequestModel) { + buildQueryData(requestNode: TaskListRequestModel) { const queryData: any = { id: requestNode.id, parentId: requestNode.parentId, diff --git a/lib/process-services-cloud/src/public-api.ts b/lib/process-services-cloud/src/public-api.ts index fa63874f47..b945932abf 100644 --- a/lib/process-services-cloud/src/public-api.ts +++ b/lib/process-services-cloud/src/public-api.ts @@ -33,6 +33,7 @@ export * from './lib/models/application-version.model'; export * from './lib/models/engine-event-cloud.model'; export * from './lib/models/task-cloud.model'; export * from './lib/models/filter-cloud-model'; +export * from './lib/models/filter-counters-cloud.model'; export * from './lib/models/task-list-sorting.model'; export * from './lib/models/process-instance-variable.model'; export * from './lib/models/variable-definition'; From b1e9d044eeeaab3e0968fe94b4a1ebc83388b0b2 Mon Sep 17 00:00:00 2001 From: Alex Molodyh <140214274+amolodyh-hyland@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:04:55 -0700 Subject: [PATCH 31/31] AAE-46057 Absorb 3px display-text overflow to prevent form tab scrollbar (#12202) --- lib/core/src/lib/form/components/form-renderer.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/core/src/lib/form/components/form-renderer.component.scss b/lib/core/src/lib/form/components/form-renderer.component.scss index 7f88f2a7f5..0e906f4d26 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.scss +++ b/lib/core/src/lib/form/components/form-renderer.component.scss @@ -33,6 +33,7 @@ .adf-form-tab-content { margin-top: 1em; + padding-bottom: 3px; } .adf-form-tab-group {