This is an automated email from the ASF dual-hosted git repository.
rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 1d41ee6d4c6 NIFI-16463 Added a rule count to UpdateAttribute Advanced
view. (#11814)
1d41ee6d4c6 is described below
commit 1d41ee6d4c62383f9f1e1039e8d31470d053e814
Author: dan-s1 <[email protected]>
AuthorDate: Wed Oct 21 08:27:16 2026 -0400
NIFI-16463 Added a rule count to UpdateAttribute Advanced view. (#11814)
* NIFI-16463 Added a rule count to UpdateAttribute Advanced view.
* NIFI-16463 Added import and corrected assertion of which text is not
present.
* NIFI-16463 Removed used of privatge method.
* NIFI-16463 Removed use of fakeAsync where tick is not used.
* NIFI-16463 Review
* NIFI-16463 Change to prevent "No Matching rules" when editing a rule.
This closes #11814
---
.../ui/rule-listing/rule-listing.component.html | 236 +++++++++++----------
.../ui/rule-listing/rule-listing.component.spec.ts | 77 ++++++-
2 files changed, 196 insertions(+), 117 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.html
b/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.html
index 9001da64ea0..56335a63386 100644
---
a/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.html
+++
b/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.html
@@ -75,125 +75,131 @@
}
</div>
</div>
- @if (filteredRulesList.length === 0 && !newRule) {
+ @if (!newRule && rulesList.length > 0) {
+ <div class="tertiary-color font-medium">
+ Displaying {{ filteredRulesList.length }} of {{
rulesList.length }}
+ </div>
+ }
+
+ @if (!newRule && rulesList.length > 0 && filteredRulesList.length ===
0) {
<div class="unset">No matching rules</div>
+ }
+
+ @if (allowRuleReordering) {
+ <mat-accordion
+ [multi]="true"
+ cdkDropList
+ [cdkDropListDisabled]="!isEditable"
+ [cdkDropListData]="rulesList"
+ (cdkDropListDropped)="reorderRules($event)">
+ @for (rule of filteredRulesList; track rule.id) {
+ <mat-expansion-panel cdkDrag
cdkDragPreviewContainer="parent">
+ <mat-expansion-panel-header
+ class="reorder-header"
+ [collapsedHeight]="'42px'"
+ [expandedHeight]="'42px'">
+ <mat-panel-title>
+ <div class="flex justify-between items-center">
+ <span class="grip -mt-0.5 mr-5
cursor-grab"></span>
+ <div class="flex-1 truncate">
+ {{ rule.name }}
+ </div>
+ </div>
+ </mat-panel-title>
+ <mat-panel-description>
+ {{ rule.comments }}
+ </mat-panel-description>
+ </mat-expansion-panel-header>
+ <edit-rule
+ [id]="rule.id"
+ [name]="rule.name"
+ [existingRuleNames]="getExistingRuleNames()"
+ [comments]="rule.comments"
+ [conditions]="rule.conditions"
+ [actions]="rule.actions"
+ [editable]="false"></edit-rule>
+ </mat-expansion-panel>
+ }
+ </mat-accordion>
} @else {
- @if (allowRuleReordering) {
- <mat-accordion
- [multi]="true"
- cdkDropList
- [cdkDropListDisabled]="!isEditable"
- [cdkDropListData]="rulesList"
- (cdkDropListDropped)="reorderRules($event)">
- @for (rule of filteredRulesList; track rule.id) {
- <mat-expansion-panel cdkDrag
cdkDragPreviewContainer="parent">
- <mat-expansion-panel-header
- class="reorder-header"
- [collapsedHeight]="'42px'"
- [expandedHeight]="'42px'">
- <mat-panel-title>
- <div class="flex justify-between
items-center">
- <span class="grip -mt-0.5 mr-5
cursor-grab"></span>
- <div class="flex-1 truncate">
- {{ rule.name }}
- </div>
+ <mat-accordion [multi]="true">
+ @for (rule of filteredRulesList; track rule.id) {
+ <mat-expansion-panel
+ #expansionPanel
+ (opened)="panelOpened()"
+ (closed)="panelClosed()"
+ [disabled]="isDirty(rule.id)">
+ <mat-expansion-panel-header
+ class="editable-header"
+ [collapsedHeight]="'42px'"
+ [expandedHeight]="'42px'">
+ <mat-panel-title>
+ {{ rule.name }}
+ </mat-panel-title>
+ <mat-panel-description>
+ <div class="flex justify-between items-center">
+ <div class="flex-1 truncate">
+ {{ rule.comments }}
</div>
- </mat-panel-title>
- <mat-panel-description>
- {{ rule.comments }}
- </mat-panel-description>
- </mat-expansion-panel-header>
- <edit-rule
- [id]="rule.id"
- [name]="rule.name"
- [existingRuleNames]="getExistingRuleNames()"
- [comments]="rule.comments"
- [conditions]="rule.conditions"
- [actions]="rule.actions"
- [editable]="false"></edit-rule>
- </mat-expansion-panel>
- }
- </mat-accordion>
- } @else {
- <mat-accordion [multi]="true">
- @for (rule of filteredRulesList; track rule.id) {
- <mat-expansion-panel
- #expansionPanel
- (opened)="panelOpened()"
- (closed)="panelClosed()"
- [disabled]="isDirty(rule.id)">
- <mat-expansion-panel-header
- class="editable-header"
- [collapsedHeight]="'42px'"
- [expandedHeight]="'42px'">
- <mat-panel-title>
- {{ rule.name }}
- </mat-panel-title>
- <mat-panel-description>
- <div class="flex justify-between
items-center">
- <div class="flex-1 truncate">
- {{ rule.comments }}
- </div>
- @if (isEditable) {
- <button
- mat-icon-button
- type="button"
-
[matMenuTriggerFor]="actionMenu"
-
(click)="$event.stopPropagation()"
-
(keydown)="$event.stopPropagation()"
- class="flex items-center
justify-center icon global-menu">
- <i class="fa
fa-ellipsis-v"></i>
+ @if (isEditable) {
+ <button
+ mat-icon-button
+ type="button"
+ [matMenuTriggerFor]="actionMenu"
+ (click)="$event.stopPropagation()"
+
(keydown)="$event.stopPropagation()"
+ class="flex items-center
justify-center icon global-menu">
+ <i class="fa fa-ellipsis-v"></i>
+ </button>
+ <mat-menu #actionMenu="matMenu"
xPosition="before">
+ <button mat-menu-item title="Clone
Rule" (click)="cloneRule(rule)">
+ <i class="fa fa-clone
primary-color mr-2"></i>
+ Clone
</button>
- <mat-menu #actionMenu="matMenu"
xPosition="before">
- <button mat-menu-item
title="Clone Rule" (click)="cloneRule(rule)">
- <i class="fa fa-clone
primary-color mr-2"></i>
- Clone
- </button>
- <button mat-menu-item
title="Delete" (click)="deleteRule(rule)">
- <i class="fa fa-trash
primary-color mr-2"></i>
- Delete
- </button>
- </mat-menu>
- }
- </div>
- </mat-panel-description>
- </mat-expansion-panel-header>
- <edit-rule
- [id]="rule.id"
- [name]="rule.name"
- [existingRuleNames]="getExistingRuleNames()"
- [comments]="rule.comments"
- [conditions]="rule.conditions"
- [actions]="rule.actions"
- [editable]="isEditable"
- [ruleUpdate]="(getRuleUpdates(rule.id) |
async)!"
- (editRule)="editRule($event)"
- (dirty)="setDirty(rule.id, $event)"
- (exit)="expansionPanel.close()"></edit-rule>
- </mat-expansion-panel>
- }
- @if (newRule) {
- <mat-expansion-panel expanded="true" disabled="true">
- <mat-expansion-panel-header>
- <mat-panel-title class="items-center">
- <span class="unset">New rule</span>
- </mat-panel-title>
- </mat-expansion-panel-header>
- <edit-rule
- class="new-rule"
- [name]="newRule.name"
- [existingRuleNames]="getExistingRuleNames()"
- [comments]="newRule.comments"
- [conditions]="newRule.conditions"
- [actions]="newRule.actions"
- [editable]="isEditable"
- (afterInit)="newRuleExpanded()"
- (addRule)="createRule($event)"
- (exit)="cancelNewRule()"></edit-rule>
- </mat-expansion-panel>
- }
- </mat-accordion>
- }
+ <button mat-menu-item
title="Delete" (click)="deleteRule(rule)">
+ <i class="fa fa-trash
primary-color mr-2"></i>
+ Delete
+ </button>
+ </mat-menu>
+ }
+ </div>
+ </mat-panel-description>
+ </mat-expansion-panel-header>
+ <edit-rule
+ [id]="rule.id"
+ [name]="rule.name"
+ [existingRuleNames]="getExistingRuleNames()"
+ [comments]="rule.comments"
+ [conditions]="rule.conditions"
+ [actions]="rule.actions"
+ [editable]="isEditable"
+ [ruleUpdate]="(getRuleUpdates(rule.id) | async)!"
+ (editRule)="editRule($event)"
+ (dirty)="setDirty(rule.id, $event)"
+ (exit)="expansionPanel.close()"></edit-rule>
+ </mat-expansion-panel>
+ }
+ @if (newRule) {
+ <mat-expansion-panel expanded="true" disabled="true">
+ <mat-expansion-panel-header>
+ <mat-panel-title class="items-center">
+ <span class="unset">New rule</span>
+ </mat-panel-title>
+ </mat-expansion-panel-header>
+ <edit-rule
+ class="new-rule"
+ [name]="newRule.name"
+ [existingRuleNames]="getExistingRuleNames()"
+ [comments]="newRule.comments"
+ [conditions]="newRule.conditions"
+ [actions]="newRule.actions"
+ [editable]="isEditable"
+ (afterInit)="newRuleExpanded()"
+ (addRule)="createRule($event)"
+ (exit)="cancelNewRule()"></edit-rule>
+ </mat-expansion-panel>
+ }
+ </mat-accordion>
}
</div>
}
diff --git
a/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.spec.ts
index dc792930260..5e590f831c4 100644
---
a/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/rule-listing/rule-listing.component.spec.ts
@@ -26,12 +26,22 @@ import { initialState as advancedUiParametersInitialState }
from '../../state/ad
import { updateAttributeFeatureKey } from '../../state';
import { advancedUiParametersFeatureKey } from
'../../state/advanced-ui-parameters';
import { evaluationContextFeatureKey } from '../../state/evaluation-context';
-import { rulesFeatureKey } from '../../state/rules';
+import { Rule, rulesFeatureKey } from '../../state/rules';
+import { fakeAsync, tick } from '@angular/core/testing';
describe('RuleListing', () => {
let component: RuleListing;
let fixture: ComponentFixture<RuleListing>;
+ const createRule = (id: string, name: string): Rule =>
+ ({
+ id,
+ name,
+ comments: '',
+ conditions: [],
+ actions: []
+ }) as Rule;
+
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RuleListing],
@@ -50,10 +60,73 @@ describe('RuleListing', () => {
fixture = TestBed.createComponent(RuleListing);
component = fixture.componentInstance;
- fixture.detectChanges();
});
it('should create', () => {
+ fixture.detectChanges();
expect(component).toBeTruthy();
});
+
+ it('should display the number of filtered rules and total rules', () => {
+ component.rules = [createRule('1', 'Rule One'), createRule('2', 'Rule
Two')];
+
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toContain('Displaying 2 of
2');
+ });
+
+ it('should display one matching rule out of four when filtered',
fakeAsync(() => {
+ component.rules = [
+ createRule('1', 'Apple'),
+ createRule('2', 'Apricot'),
+ createRule('3', 'Banana'),
+ createRule('4', 'Cherry')
+ ];
+
+ component.searchForm.get('searchRules')?.setValue('Ba');
+ tick(500);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toContain('Displaying 1 of
4');
+ }));
+
+ it('should display two matching rules out of four when filtered',
fakeAsync(() => {
+ component.rules = [
+ createRule('1', 'Apple'),
+ createRule('2', 'Apricot'),
+ createRule('3', 'Banana'),
+ createRule('4', 'Cherry')
+ ];
+
+ component.searchForm.get('searchRules')?.setValue('Ap');
+ tick(500);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toContain('Displaying 2 of
4');
+ }));
+
+ it('should display zero matching rules when no rules match the search',
fakeAsync(() => {
+ component.rules = [
+ createRule('1', 'Apple'),
+ createRule('2', 'Apricot'),
+ createRule('3', 'Banana'),
+ createRule('4', 'Cherry')
+ ];
+
+ component.searchForm.get('searchRules')?.setValue('Plum');
+ tick(500);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toContain('Displaying 0 of
4');
+ expect(fixture.nativeElement.textContent).toContain('No matching
rules');
+ }));
+
+ it('should display nothing when there are no rules', () => {
+ component.rules = [];
+
+ fixture.detectChanges();
+
+ // The empty-list template may not display the count at all.
+ expect(fixture.nativeElement.textContent).not.toContain('Displaying');
+ });
});