From 03ed8e071aa96539041f8950d6a0dbe219c8a469 Mon Sep 17 00:00:00 2001
From: Thomas Hunter <thomas.hunter@hyland.com>
Date: Tue, 22 Nov 2022 11:14:09 +0000
Subject: [PATCH] =?UTF-8?q?=EF=BB=BF[ACS-4010]=20Rule=20sets=20listing=20r?=
 =?UTF-8?q?egrouping=20(#2803)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [ACS-4010] Rule sets listing regrouping

* Linting

* Unit tests

* Remove TODOs
---
 projects/aca-folder-rules/assets/i18n/en.json |  15 ++-
 .../src/lib/folder-rules.module.ts            |   8 +-
 .../manage-rules.smart-component.html         |  42 ++++--
 .../manage-rules.smart-component.spec.ts      |  56 ++++++--
 .../manage-rules.smart-component.ts           |  50 +++-----
 .../src/lib/mock/rule-sets.mock.ts            |  17 ++-
 .../src/lib/mock/rules.mock.ts                |  12 ++
 .../src/lib/model/rule-grouping-item.model.ts |  40 ++++++
 .../rule-list-grouping.ui-component.html      |  47 +++++++
 .../rule-list-grouping.ui-component.scss      |  27 ++++
 .../rule-list-grouping.ui-component.spec.ts   |  71 ++++++++++
 .../rule-list-grouping.ui-component.ts}       |  91 ++++---------
 .../rule-list-item.ui-component.html          |   8 +-
 .../rule-list-item.ui-component.ts            |   1 +
 .../rule-list/rule-list.ui-component.html     |  89 +++++++++++--
 .../rule-list/rule-list.ui-component.scss     |  48 +++++++
 .../rule-list/rule-list.ui-component.spec.ts  |  47 +++----
 .../rule-list/rule-list.ui-component.ts       |  85 ++++++++++--
 .../rule-set-list.ui-component.html           |  90 -------------
 .../rule-set-list.ui-component.scss           |  78 -----------
 .../rule-set-list.ui-component.spec.ts        |  76 -----------
 .../services/folder-rule-sets.service.spec.ts |  32 +++--
 .../lib/services/folder-rule-sets.service.ts  | 121 ++++++++++++++----
 .../lib/services/folder-rules.service.spec.ts |  12 +-
 .../src/lib/services/folder-rules.service.ts  |  28 ++--
 25 files changed, 719 insertions(+), 472 deletions(-)
 create mode 100644 projects/aca-folder-rules/src/lib/model/rule-grouping-item.model.ts
 create mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.html
 create mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.scss
 create mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.spec.ts
 rename projects/aca-folder-rules/src/lib/rule-list/{rule-set-list/rule-set-list.ui-component.ts => rule-list-grouping/rule-list-grouping.ui-component.ts} (54%)
 delete mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.html
 delete mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.scss
 delete mode 100644 projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.spec.ts

diff --git a/projects/aca-folder-rules/assets/i18n/en.json b/projects/aca-folder-rules/assets/i18n/en.json
index 574e57a2c..529d0e798 100644
--- a/projects/aca-folder-rules/assets/i18n/en.json
+++ b/projects/aca-folder-rules/assets/i18n/en.json
@@ -92,7 +92,8 @@
         },
         "ACTIONS": {
           "CREATE_RULE": "Create rule",
-          "EDIT_RULE": "Edit"
+          "EDIT_RULE": "Edit",
+          "SEE_IN_FOLDER": "See in folder"
         }
       },
       "EMPTY_RULES_LIST": {
@@ -107,13 +108,13 @@
       }
     },
     "RULE_LIST": {
-      "OWNED_BY_THIS_FOLDER": "Owned by this folder",
-      "LINKED_FROM": "Linked from",
-      "INHERITED_FROM": "Inherited from",
-      "LOAD_MORE_RULE_SETS": "Load more rule sets",
-      "LOADING_RULE_SETS": "Loading rule sets",
+      "OWNED_RULES": "Rules from current folder",
+      "LINKED_RULES": "Rules from linked folder",
+      "INHERITED_RULES": "Inherited rules",
+      "LOAD_MORE_RULE_SETS": "Load rules from other folders",
       "LOAD_MORE_RULES": "Load more rules",
-      "LOADING_RULES": "Loading rules"
+      "LOADING_RULES": "Loading rules",
+      "INHERITED_RULES_WILL_BE_RUN_FIRST": "Inherited rules will be run first"
     }
   }
 }
diff --git a/projects/aca-folder-rules/src/lib/folder-rules.module.ts b/projects/aca-folder-rules/src/lib/folder-rules.module.ts
index e55904991..5b1537504 100644
--- a/projects/aca-folder-rules/src/lib/folder-rules.module.ts
+++ b/projects/aca-folder-rules/src/lib/folder-rules.module.ts
@@ -37,12 +37,12 @@ import { RuleSimpleConditionUiComponent } from './rule-details/conditions/rule-s
 import { GenericErrorModule, PageLayoutModule } from '@alfresco/aca-shared';
 import { BreadcrumbModule, DocumentListModule } from '@alfresco/adf-content-services';
 import { RuleListItemUiComponent } from './rule-list/rule-list-item/rule-list-item.ui-component';
-import { RuleListUiComponent } from './rule-list/rule-list/rule-list.ui-component';
+import { RuleListGroupingUiComponent } from './rule-list/rule-list-grouping/rule-list-grouping.ui-component';
 import { RuleTriggersUiComponent } from './rule-details/triggers/rule-triggers.ui-component';
 import { RuleOptionsUiComponent } from './rule-details/options/rule-options.ui-component';
 import { RuleActionListUiComponent } from './rule-details/actions/rule-action-list.ui-component';
 import { RuleActionUiComponent } from './rule-details/actions/rule-action.ui-component';
-import { RuleSetListUiComponent } from './rule-list/rule-set-list/rule-set-list.ui-component';
+import { RuleListUiComponent } from './rule-list/rule-list/rule-list.ui-component';
 
 const routes: Routes = [
   {
@@ -70,9 +70,9 @@ const routes: Routes = [
     RuleActionUiComponent,
     RuleCompositeConditionUiComponent,
     RuleDetailsUiComponent,
-    RuleListUiComponent,
+    RuleListGroupingUiComponent,
     RuleListItemUiComponent,
-    RuleSetListUiComponent,
+    RuleListUiComponent,
     RuleSimpleConditionUiComponent,
     RuleTriggersUiComponent,
     RuleOptionsUiComponent
diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html
index e8e5ac85e..18d674bdd 100644
--- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html
+++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.html
@@ -12,36 +12,44 @@
   <aca-page-layout-content>
     <div class="main-content">
 
-      <ng-container *ngIf="(ruleSetsLoading$ | async) || (actionsLoading$ | async); else onLoaded">
+      <ng-container *ngIf="((ruleSetsLoading$ | async) && (inheritedRuleSets$ | async).length === 0) || (actionsLoading$ | async); else onLoaded">
         <mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
       </ng-container>
 
       <ng-template #onLoaded>
         <ng-container *ngIf="folderInfo$ | async; else genericError">
           <adf-toolbar class="adf-toolbar--inline aca-manage-rules__actions-bar">
+
             <adf-toolbar-title class="aca-manage-rules__actions-bar__title">
               <mat-icon class="icon-aligner">folder</mat-icon>
               <adf-breadcrumb root="{{ (folderInfo$ | async).name }}:{{'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.BREADCRUMB.RULES' | translate}}"
                               class="aca-manage-rules__actions-bar__title__breadcrumb"></adf-breadcrumb>
             </adf-toolbar-title>
 
-            <button mat-flat-button color="primary" (click)="openCreateUpdateRuleDialog()">{{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.CREATE_RULE' | translate }}</button>
+            <button
+              *ngIf="canEditRule(mainRuleSet$ | async)"
+              data-automation-id="manage-rules-create-button"
+              mat-flat-button color="primary"
+              (click)="openCreateUpdateRuleDialog()">
+              {{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.CREATE_RULE' | translate }}
+            </button>
+
           </adf-toolbar>
           <mat-divider></mat-divider>
 
-          <div class="aca-manage-rules__container" *ngIf="(ruleSetListing$ | async).length > 0; else emptyContent">
-            <aca-rule-set-list
+          <div class="aca-manage-rules__container" *ngIf="(mainRuleSet$ | async) || (inheritedRuleSets$ | async).length > 0; else emptyContent">
+            <aca-rule-list
+              [mainRuleSet]="mainRuleSet$ | async"
               [folderId]="nodeId"
-              [ruleSets]="ruleSetListing$ | async"
+              [inheritedRuleSets]="inheritedRuleSets$ | async"
               [hasMoreRuleSets]="hasMoreRuleSets$ | async"
               [ruleSetsLoading]="ruleSetsLoading$ | async"
               [selectedRule]="selectedRule$ | async"
               (loadMoreRuleSets)="onLoadMoreRuleSets()"
               (loadMoreRules)="onLoadMoreRules($event)"
-              (navigateToOtherFolder)="onNavigateToOtherFolder($event)"
               (selectRule)="onSelectRule($event)"
               (ruleEnabledChanged)="onRuleEnabledToggle($event[0], $event[1])">
-            </aca-rule-set-list>
+            </aca-rule-list>
 
             <div class="aca-manage-rules__container__rule-details">
 
@@ -56,12 +64,20 @@
                 </div>
 
                 <div class="aca-manage-rules__container__rule-details__header__buttons">
-                  <button mat-stroked-button (click)="onRuleDeleteButtonClicked(selectedRule)" id="delete-rule-btn">
-                    <mat-icon>delete_outline</mat-icon>
-                  </button>
-                  <button mat-stroked-button (click)="openCreateUpdateRuleDialog(selectedRule)" id="edit-rule-btn">
-                    {{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.EDIT_RULE' | translate }}
-                  </button>
+                  <ng-container *ngIf="canEditRule(selectedRuleSet$ | async); else goToFolderButton">
+                    <button mat-stroked-button (click)="onRuleDeleteButtonClicked(selectedRule)" id="delete-rule-btn">
+                      <mat-icon>delete_outline</mat-icon>
+                    </button>
+                    <button mat-stroked-button (click)="openCreateUpdateRuleDialog(selectedRule)" id="edit-rule-btn">
+                      {{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.EDIT_RULE' | translate }}
+                    </button>
+                  </ng-container>
+
+                  <ng-template #goToFolderButton>
+                    <button mat-stroked-button [routerLink]="['/nodes', (selectedRuleSet$ | async).owningFolder.id, 'rules']">
+                      {{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.SEE_IN_FOLDER' | translate }}
+                    </button>
+                  </ng-template>
                 </div>
               </div>
 
diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts
index 39614828c..13052ef7e 100644
--- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts
+++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.spec.ts
@@ -30,7 +30,7 @@ import { CoreTestingModule } from '@alfresco/adf-core';
 import { FolderRulesService } from '../services/folder-rules.service';
 import { ActivatedRoute } from '@angular/router';
 import { of } from 'rxjs';
-import { ruleSetsMock } from '../mock/rule-sets.mock';
+import { inheritedRuleSetMock, ownedRuleSetMock, ruleSetWithLinkMock } from '../mock/rule-sets.mock';
 import { By } from '@angular/platform-browser';
 import { owningFolderIdMock, owningFolderMock } from '../mock/node.mock';
 import { MatDialog } from '@angular/material/dialog';
@@ -74,7 +74,8 @@ describe('ManageRulesSmartComponent', () => {
     const loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.stub();
 
     folderRuleSetsService.folderInfo$ = of(owningFolderMock);
-    folderRuleSetsService.ruleSetListing$ = of(ruleSetsMock);
+    folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
+    folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
     folderRuleSetsService.isLoading$ = of(false);
     folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
     actionsService.loading$ = of(false);
@@ -85,20 +86,21 @@ describe('ManageRulesSmartComponent', () => {
 
     expect(loadRuleSetsSpy).toHaveBeenCalledOnceWith(component.nodeId);
 
-    const ruleSets = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
+    const ruleGroupingSections = debugElement.queryAll(By.css(`[data-automation-id="rule-list-item"]`));
     const rules = debugElement.queryAll(By.css('.aca-rule-list-item'));
     const ruleDetails = debugElement.query(By.css('aca-rule-details'));
     const deleteRuleBtn = debugElement.query(By.css('#delete-rule-btn'));
 
-    expect(ruleSets.length).toBe(3, 'unexpected number of rule sets');
-    expect(rules.length).toBe(6, 'unexpected number of aca-rule-list-item');
+    expect(ruleGroupingSections.length).toBe(2, 'unexpected number of rule sections');
+    expect(rules.length).toBe(4, 'unexpected number of aca-rule-list-item');
     expect(ruleDetails).toBeTruthy('aca-rule-details was not rendered');
     expect(deleteRuleBtn).toBeTruthy('no delete rule button');
   });
 
   it('should only show adf-empty-content if node has no rules defined yet', () => {
     folderRuleSetsService.folderInfo$ = of(owningFolderMock);
-    folderRuleSetsService.ruleSetListing$ = of([]);
+    folderRuleSetsService.mainRuleSet$ = of(null);
+    folderRuleSetsService.inheritedRuleSets$ = of([]);
     folderRuleSetsService.isLoading$ = of(false);
     actionsService.loading$ = of(false);
 
@@ -117,7 +119,8 @@ describe('ManageRulesSmartComponent', () => {
 
   it('should only show aca-generic-error if the non-existing node was provided', () => {
     folderRuleSetsService.folderInfo$ = of(null);
-    folderRuleSetsService.ruleSetListing$ = of([]);
+    folderRuleSetsService.mainRuleSet$ = of(null);
+    folderRuleSetsService.inheritedRuleSets$ = of([]);
     folderRuleSetsService.isLoading$ = of(false);
     actionsService.loading$ = of(false);
 
@@ -136,7 +139,8 @@ describe('ManageRulesSmartComponent', () => {
 
   it('should only show progress bar while loading', async () => {
     folderRuleSetsService.folderInfo$ = of(null);
-    folderRuleSetsService.ruleSetListing$ = of([]);
+    folderRuleSetsService.mainRuleSet$ = of(null);
+    folderRuleSetsService.inheritedRuleSets$ = of([]);
     folderRuleSetsService.isLoading$ = of(true);
     actionsService.loading$ = of(true);
 
@@ -156,7 +160,8 @@ describe('ManageRulesSmartComponent', () => {
   it('should call deleteRule() if confirmation dialog returns true', () => {
     const dialog = TestBed.inject(MatDialog);
     folderRuleSetsService.folderInfo$ = of(owningFolderMock);
-    folderRuleSetsService.ruleSetListing$ = of(ruleSetsMock);
+    folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
+    folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
     folderRuleSetsService.isLoading$ = of(false);
     folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
     folderRulesService.deletedRuleId$ = of(null);
@@ -191,4 +196,37 @@ describe('ManageRulesSmartComponent', () => {
     expect(ruleDetails).toBeTruthy('expected ruleDetails');
     expect(deleteRuleBtn).toBeTruthy();
   });
+
+  describe('Create rule button visibility', () => {
+    beforeEach(() => {
+      folderRuleSetsService.folderInfo$ = of(owningFolderMock);
+      folderRuleSetsService.inheritedRuleSets$ = of([]);
+      folderRuleSetsService.isLoading$ = of(false);
+      actionsService.loading$ = of(false);
+    });
+
+    it('should show the create rule button if there is no main rule set', () => {
+      folderRuleSetsService.mainRuleSet$ = of(null);
+      fixture.detectChanges();
+
+      const createButton = debugElement.query(By.css(`[data-automation-id="manage-rules-create-button"]`));
+      expect(createButton).toBeTruthy();
+    });
+
+    it('should show the create rule button if the main rule set is owned', () => {
+      folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
+      fixture.detectChanges();
+
+      const createButton = debugElement.query(By.css(`[data-automation-id="manage-rules-create-button"]`));
+      expect(createButton).toBeTruthy();
+    });
+
+    it('should not show the create rule button if the main rule set is linked', () => {
+      folderRuleSetsService.mainRuleSet$ = of(ruleSetWithLinkMock);
+      fixture.detectChanges();
+
+      const createButton = debugElement.query(By.css(`[data-automation-id="manage-rules-create-button"]`));
+      expect(createButton).toBeFalsy();
+    });
+  });
 });
diff --git a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.ts b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.ts
index a46d99af4..aca129d01 100644
--- a/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.ts
+++ b/projects/aca-folder-rules/src/lib/manage-rules/manage-rules.smart-component.ts
@@ -29,7 +29,7 @@ import { FolderRulesService } from '../services/folder-rules.service';
 import { Observable, Subject } from 'rxjs';
 import { Rule } from '../model/rule.model';
 import { ActivatedRoute } from '@angular/router';
-import { AppStore, NavigateRouteAction, NodeInfo } from '@alfresco/aca-shared/store';
+import { NodeInfo } from '@alfresco/aca-shared/store';
 import { delay, takeUntil } from 'rxjs/operators';
 import { EditRuleDialogSmartComponent } from '../rule-details/edit-rule-dialog.smart-component';
 import { MatDialog } from '@angular/material/dialog';
@@ -38,7 +38,6 @@ import { NotificationService } from '@alfresco/adf-core';
 import { ActionDefinitionTransformed } from '../model/rule-action.model';
 import { ActionsService } from '../services/actions.service';
 import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
-import { Store } from '@ngrx/store';
 import { RuleSet } from '../model/rule-set.model';
 
 @Component({
@@ -51,8 +50,10 @@ import { RuleSet } from '../model/rule-set.model';
 export class ManageRulesSmartComponent implements OnInit, OnDestroy {
   nodeId: string = null;
 
-  ruleSetListing$: Observable<RuleSet[]>;
+  mainRuleSet$: Observable<RuleSet>;
+  inheritedRuleSets$: Observable<RuleSet[]>;
   selectedRule$: Observable<Rule>;
+  selectedRuleSet$: Observable<RuleSet>;
   hasMoreRuleSets$: Observable<boolean>;
   ruleSetsLoading$: Observable<boolean>;
   folderInfo$: Observable<NodeInfo>;
@@ -69,13 +70,14 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
     private matDialogService: MatDialog,
     private notificationService: NotificationService,
     private actionsService: ActionsService,
-    private folderRuleSetsService: FolderRuleSetsService,
-    private store: Store<AppStore>
+    private folderRuleSetsService: FolderRuleSetsService
   ) {}
 
   ngOnInit() {
-    this.ruleSetListing$ = this.folderRuleSetsService.ruleSetListing$;
+    this.mainRuleSet$ = this.folderRuleSetsService.mainRuleSet$;
+    this.inheritedRuleSets$ = this.folderRuleSetsService.inheritedRuleSets$;
     this.selectedRule$ = this.folderRulesService.selectedRule$;
+    this.selectedRuleSet$ = this.folderRuleSetsService.selectedRuleSet$;
     this.hasMoreRuleSets$ = this.folderRuleSetsService.hasMoreRuleSets$;
     this.ruleSetsLoading$ = this.folderRuleSetsService.isLoading$;
     this.folderInfo$ = this.folderRuleSetsService.folderInfo$;
@@ -136,24 +138,17 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
   }
 
   async onRuleUpdate(rule: Rule) {
-    const ruleSet = this.folderRuleSetsService.getRuleSetFromRuleId(rule.id);
-    await this.folderRulesService.updateRule(this.nodeId, rule.id, rule, ruleSet.id);
-    this.folderRulesService.loadRules(ruleSet, 0, rule);
+    const newRule = await this.folderRulesService.updateRule(this.nodeId, rule.id, rule);
+    this.folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
   }
 
   async onRuleCreate(ruleCreateParams: Partial<Rule>) {
-    await this.folderRulesService.createRule(this.nodeId, ruleCreateParams, '-default-');
-    const ruleSetToLoad = this.folderRuleSetsService.getOwnedOrLinkedRuleSet();
-    if (ruleSetToLoad) {
-      this.folderRulesService.loadRules(ruleSetToLoad, 0, 'last');
-    } else {
-      this.folderRuleSetsService.loadMoreRuleSets(true);
-    }
+    const newRule = await this.folderRulesService.createRule(this.nodeId, ruleCreateParams);
+    this.folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
   }
 
   async onRuleEnabledToggle(rule: Rule, isEnabled: boolean) {
-    const ruleSet = this.folderRuleSetsService.getRuleSetFromRuleId(rule.id);
-    await this.folderRulesService.updateRule(this.nodeId, rule.id, { ...rule, isEnabled }, ruleSet.id);
+    await this.folderRulesService.updateRule(this.nodeId, rule.id, { ...rule, isEnabled });
   }
 
   onRuleDeleteButtonClicked(rule: Rule) {
@@ -174,25 +169,18 @@ export class ManageRulesSmartComponent implements OnInit, OnDestroy {
   }
 
   onRuleDelete(deletedRuleId: string) {
-    if (deletedRuleId) {
-      const folderToRefresh = this.folderRuleSetsService.getRuleSetFromRuleId(deletedRuleId);
-      if (folderToRefresh?.rules.length > 1) {
-        this.folderRulesService.loadRules(folderToRefresh, 0, 'first');
-      } else {
-        this.folderRuleSetsService.loadRuleSets(this.nodeId);
-      }
-    }
-  }
-
-  onNavigateToOtherFolder(nodeId) {
-    this.store.dispatch(new NavigateRouteAction(['nodes', nodeId, 'rules']));
+    this.folderRuleSetsService.removeRuleFromMainRuleSet(deletedRuleId);
   }
 
   onLoadMoreRuleSets() {
-    this.folderRuleSetsService.loadMoreRuleSets();
+    this.folderRuleSetsService.loadMoreInheritedRuleSets();
   }
 
   onLoadMoreRules(ruleSet: RuleSet) {
     this.folderRulesService.loadRules(ruleSet);
   }
+
+  canEditRule(ruleSet: RuleSet): boolean {
+    return !ruleSet || FolderRuleSetsService.isOwnedRuleSet(ruleSet, this.nodeId);
+  }
 }
diff --git a/projects/aca-folder-rules/src/lib/mock/rule-sets.mock.ts b/projects/aca-folder-rules/src/lib/mock/rule-sets.mock.ts
index e7c47603b..d0d4d45ca 100644
--- a/projects/aca-folder-rules/src/lib/mock/rule-sets.mock.ts
+++ b/projects/aca-folder-rules/src/lib/mock/rule-sets.mock.ts
@@ -66,6 +66,15 @@ export const getRuleSetsResponseMock = {
   }
 };
 
+export const getDefaultRuleSetResponseMock = {
+  entry: {
+    linkedToBy: [],
+    owningFolder: owningFolderIdMock,
+    isLinkedTo: false,
+    id: 'rule-set-no-links'
+  }
+};
+
 export const ruleSetMock = (rules: Rule[] = []): RuleSet => ({
   id: 'rule-set-id',
   isLinkedTo: false,
@@ -76,7 +85,7 @@ export const ruleSetMock = (rules: Rule[] = []): RuleSet => ({
   loadingRules: false
 });
 
-const ruleSetWithNoLinksMock: RuleSet = {
+export const ownedRuleSetMock: RuleSet = {
   id: 'rule-set-no-links',
   isLinkedTo: false,
   owningFolder: owningFolderMock,
@@ -86,7 +95,7 @@ const ruleSetWithNoLinksMock: RuleSet = {
   loadingRules: false
 };
 
-const ruleSetWithLinkMock: RuleSet = {
+export const ruleSetWithLinkMock: RuleSet = {
   id: 'rule-set-with-link',
   isLinkedTo: true,
   owningFolder: otherFolderMock,
@@ -96,7 +105,7 @@ const ruleSetWithLinkMock: RuleSet = {
   loadingRules: false
 };
 
-const inheritedRuleSetMock: RuleSet = {
+export const inheritedRuleSetMock: RuleSet = {
   id: 'inherited-rule-set',
   isLinkedTo: false,
   owningFolder: otherFolderMock,
@@ -106,4 +115,4 @@ const inheritedRuleSetMock: RuleSet = {
   loadingRules: false
 };
 
-export const ruleSetsMock: RuleSet[] = [inheritedRuleSetMock, ruleSetWithNoLinksMock, ruleSetWithLinkMock];
+export const ruleSetsMock: RuleSet[] = [inheritedRuleSetMock, ownedRuleSetMock, ruleSetWithLinkMock];
diff --git a/projects/aca-folder-rules/src/lib/mock/rules.mock.ts b/projects/aca-folder-rules/src/lib/mock/rules.mock.ts
index 52b7a67fb..295a196f8 100644
--- a/projects/aca-folder-rules/src/lib/mock/rules.mock.ts
+++ b/projects/aca-folder-rules/src/lib/mock/rules.mock.ts
@@ -24,6 +24,7 @@
  */
 
 import { Rule } from '../model/rule.model';
+import { RuleGroupingItem } from '../model/rule-grouping-item.model';
 
 export const getRulesResponseMock = {
   list: {
@@ -158,3 +159,14 @@ export const manyRulesMock: Rule[] = [ruleMock('rule1'), ruleMock('rule2'), rule
 export const ownedRulesMock: Rule[] = [ruleMock('owned-rule-1'), ruleMock('owned-rule-2')];
 export const linkedRulesMock: Rule[] = [ruleMock('linked-rule-1'), ruleMock('linked-rule-2')];
 export const inheritedRulesMock: Rule[] = [ruleMock('inherited-rule-1'), ruleMock('inherited-rule-2')];
+
+export const ruleListGroupingItemsMock: RuleGroupingItem[] = [
+  {
+    type: 'rule',
+    rule: ruleMock('rule1')
+  },
+  {
+    type: 'rule',
+    rule: ruleMock('rule2')
+  }
+];
diff --git a/projects/aca-folder-rules/src/lib/model/rule-grouping-item.model.ts b/projects/aca-folder-rules/src/lib/model/rule-grouping-item.model.ts
new file mode 100644
index 000000000..4b7581084
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/model/rule-grouping-item.model.ts
@@ -0,0 +1,40 @@
+/*!
+ * @license
+ * Alfresco Example Content Application
+ *
+ * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ *
+ * This file is part of the Alfresco Example Content Application.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail.  Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Alfresco Example Content Application is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { Rule } from './rule.model';
+import { RuleSet } from './rule-set.model';
+
+export type RuleGroupingItem =
+  | {
+      type: 'rule';
+      rule: Rule;
+    }
+  | {
+      type: 'load-more-rules';
+      ruleSet: RuleSet;
+    }
+  | {
+      type: 'loading' | 'load-more-rule-sets';
+    };
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.html b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.html
new file mode 100644
index 000000000..0f2a6beba
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.html
@@ -0,0 +1,47 @@
+<ng-container *ngFor="let item of items">
+
+  <aca-rule-list-item
+    *ngIf="item.type === 'rule'; else loadMoreRules"
+    matRipple matRippleColor="hsla(0,0%,0%,0.05)"
+    tabindex="0"
+    [rule]="item.rule"
+    [isSelected]="isSelected(item.rule)"
+    (click)="onRuleClicked(item.rule)"
+    (enabledChanged)="onEnabledChanged(item.rule, $event)">
+  </aca-rule-list-item>
+
+  <ng-template #loadMoreRules>
+    <div
+      *ngIf="item.type === 'load-more-rules'; else loadMoreRuleSets"
+      tabindex="0"
+      class="aca-rule-list-grouping__non-rule-item load-more"
+      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
+      (click)="onClickLoadMoreRules(item.ruleSet)"
+      (keyup.enter)="onClickLoadMoreRules(item.ruleSet)">
+      {{ 'ACA_FOLDER_RULES.RULE_LIST.LOAD_MORE_RULES' | translate }}
+    </div>
+  </ng-template>
+
+  <ng-template #loadMoreRuleSets>
+    <div
+      *ngIf="item.type === 'load-more-rule-sets'; else loadingRules"
+      tabindex="0"
+      class="aca-rule-list-grouping__non-rule-item load-more"
+      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
+      (click)="onClickLoadMoreRuleSets()"
+      (keyup.enter)="onClickLoadMoreRuleSets()">
+      {{ 'ACA_FOLDER_RULES.RULE_LIST.LOAD_MORE_RULE_SETS' | translate }}
+    </div>
+  </ng-template>
+
+  <ng-template #loadingRules>
+    <div
+      tabindex="0"
+      class="aca-rule-list-grouping__non-rule-item"
+      matRipple matRippleColor="hsla(0,0%,0%,0.05)">
+      <mat-spinner mode="indeterminate" [diameter]="16"></mat-spinner>
+      {{ 'ACA_FOLDER_RULES.RULE_LIST.LOADING_RULES' | translate }}
+    </div>
+  </ng-template>
+
+</ng-container>
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.scss
new file mode 100644
index 000000000..bfb12d73f
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.scss
@@ -0,0 +1,27 @@
+.aca-rule-list-grouping {
+  display: flex;
+  flex-direction: column;
+
+  &__non-rule-item {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    color: var(--theme-disabled-text-color);
+    font-style: italic;
+    text-align: center;
+    padding: 0.5em 0;
+
+    &.load-more {
+      cursor: pointer;
+    }
+
+    &:not(:last-child) {
+      border-bottom: 1px solid var(--theme-border-color);
+    }
+
+    .mat-spinner {
+      margin-right: 0.5em;
+    }
+  }
+}
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.spec.ts
new file mode 100644
index 000000000..e3dd30c14
--- /dev/null
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.spec.ts
@@ -0,0 +1,71 @@
+/*!
+ * @license
+ * Alfresco Example Content Application
+ *
+ * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ *
+ * This file is part of the Alfresco Example Content Application.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail.  Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Alfresco Example Content Application is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RuleListGroupingUiComponent } from './rule-list-grouping.ui-component';
+import { ruleListGroupingItemsMock, rulesMock } from '../../mock/rules.mock';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { CoreTestingModule } from '@alfresco/adf-core';
+import { AcaFolderRulesModule } from '@alfresco/aca-folder-rules';
+
+describe('RuleListGroupingUiComponent', () => {
+  let component: RuleListGroupingUiComponent;
+  let fixture: ComponentFixture<RuleListGroupingUiComponent>;
+  let debugElement: DebugElement;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [CoreTestingModule, AcaFolderRulesModule],
+      declarations: [RuleListGroupingUiComponent]
+    });
+
+    fixture = TestBed.createComponent(RuleListGroupingUiComponent);
+    component = fixture.componentInstance;
+    debugElement = fixture.debugElement;
+  });
+
+  it('should display the list of rules', () => {
+    expect(component).toBeTruthy();
+
+    component.items = ruleListGroupingItemsMock;
+
+    fixture.detectChanges();
+
+    const rules = debugElement.queryAll(By.css('.aca-rule-list-item'));
+
+    expect(rules).toBeTruthy('Could not find rules');
+    expect(rules.length).toBe(2, 'Unexpected number of rules');
+
+    const rule = debugElement.query(By.css('.aca-rule-list-item:first-child'));
+    const name = rule.query(By.css('.aca-rule-list-item__header__name'));
+    const description = rule.query(By.css('.aca-rule-list-item__description'));
+    const toggleBtn = rule.query(By.css('mat-slide-toggle'));
+
+    expect(name.nativeElement.textContent).toBe(rulesMock[0].name);
+    expect(toggleBtn).toBeTruthy();
+    expect(description.nativeElement.textContent).toBe(rulesMock[0].description);
+  });
+});
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.ts
similarity index 54%
rename from projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.ts
rename to projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.ts
index 7a980e7de..d5820feb6 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.ts
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-grouping/rule-list-grouping.ui-component.ts
@@ -24,84 +24,49 @@
  */
 
 import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
-import { RuleSet } from '../../model/rule-set.model';
-import { NodeInfo } from '@alfresco/aca-shared/store';
 import { Rule } from '../../model/rule.model';
+import { RuleGroupingItem } from '../../model/rule-grouping-item.model';
+import { RuleSet } from '../../model/rule-set.model';
 
 @Component({
-  selector: 'aca-rule-set-list',
-  templateUrl: './rule-set-list.ui-component.html',
-  styleUrls: ['./rule-set-list.ui-component.scss'],
+  selector: 'aca-rule-list-grouping',
+  templateUrl: 'rule-list-grouping.ui-component.html',
+  styleUrls: ['rule-list-grouping.ui-component.scss'],
   encapsulation: ViewEncapsulation.None,
-  host: { class: 'aca-rule-set-list' }
+  host: { class: 'aca-rule-list-grouping' }
 })
-export class RuleSetListUiComponent {
+export class RuleListGroupingUiComponent {
   @Input()
-  folderId = '';
-  private _ruleSets: RuleSet[] = [];
+  items: RuleGroupingItem[] = [];
   @Input()
-  get ruleSets(): RuleSet[] {
-    return this._ruleSets;
-  }
-  set ruleSets(value: RuleSet[]) {
-    this._ruleSets = value;
-    this.expandedRuleSets = [...value];
-  }
-  @Input()
-  hasMoreRuleSets = false;
-  @Input()
-  ruleSetsLoading = false;
-  @Input()
-  selectedRule = null;
+  selectedRule: Rule = null;
 
-  @Output()
-  navigateToOtherFolder = new EventEmitter<string>();
-  @Output()
-  loadMoreRuleSets = new EventEmitter<void>();
-  @Output()
-  loadMoreRules = new EventEmitter<RuleSet>();
   @Output()
   selectRule = new EventEmitter<Rule>();
   @Output()
   ruleEnabledChanged = new EventEmitter<[Rule, boolean]>();
+  @Output()
+  loadMoreRules = new EventEmitter<RuleSet>();
+  @Output()
+  loadMoreRuleSets = new EventEmitter<void>();
 
-  expandedRuleSets: RuleSet[] = [];
-
-  isRuleSetLinked(ruleSet: RuleSet): boolean {
-    return ruleSet.linkedToBy.indexOf(this.folderId) > -1;
-  }
-
-  isRuleSetExpanded(ruleSet: RuleSet): boolean {
-    return this.expandedRuleSets.indexOf(ruleSet) > -1;
-  }
-
-  clickRuleSetHeader(ruleSet: RuleSet) {
-    if (this.isRuleSetExpanded(ruleSet)) {
-      this.expandedRuleSets.splice(this.expandedRuleSets.indexOf(ruleSet), 1);
-    } else {
-      this.expandedRuleSets.push(ruleSet);
-    }
-  }
-
-  clickNavigateButton(folder: NodeInfo) {
-    if (folder && folder.id) {
-      this.navigateToOtherFolder.emit(folder.id);
-    }
-  }
-
-  clickLoadMoreRuleSets() {
-    this.loadMoreRuleSets.emit();
-  }
-
-  clickLoadMoreRules(ruleSet: RuleSet) {
-    this.loadMoreRules.emit(ruleSet);
-  }
-
-  onSelectRule(rule: Rule) {
+  onRuleClicked(rule: Rule): void {
     this.selectRule.emit(rule);
   }
 
-  onRuleEnabledChanged(event: [Rule, boolean]) {
-    this.ruleEnabledChanged.emit(event);
+  isSelected(rule): boolean {
+    return rule.id === this.selectedRule?.id;
+  }
+
+  onEnabledChanged(rule: Rule, isEnabled: boolean) {
+    this.ruleEnabledChanged.emit([rule, isEnabled]);
+  }
+
+  onClickLoadMoreRules(ruleSet: RuleSet) {
+    this.loadMoreRules.emit(ruleSet);
+  }
+
+  onClickLoadMoreRuleSets() {
+    this.loadMoreRuleSets.emit();
   }
 }
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.html b/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.html
index 6d878afce..01fd0064a 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.html
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.html
@@ -1,5 +1,11 @@
 <div class="aca-rule-list-item__header">
+
   <span class="aca-rule-list-item__header__name">{{ rule.name }}</span>
-  <mat-slide-toggle [(ngModel)]="rule.isEnabled" (click)="onToggleClick(!rule.isEnabled, $event)"></mat-slide-toggle>
+
+  <mat-slide-toggle
+    [checked]="rule.isEnabled"
+    (click)="onToggleClick(!rule.isEnabled, $event)">
+  </mat-slide-toggle>
+
 </div>
 <div class="aca-rule-list-item__description">{{ rule.description }}</div>
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.ts
index 0af44c3b3..49f7cc679 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.ts
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list-item/rule-list-item.ui-component.ts
@@ -45,6 +45,7 @@ export class RuleListItemUiComponent {
 
   onToggleClick(isEnabled: boolean, event: Event) {
     event.stopPropagation();
+    this.rule.isEnabled = !this.rule.isEnabled;
     this.enabledChanged.emit(isEnabled);
   }
 }
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.html b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.html
index bf0ccdc31..0b88f4069 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.html
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.html
@@ -1,11 +1,80 @@
-<div class="aca-rules-list" >
-  <aca-rule-list-item
-    matRipple matRippleColor="hsla(0,0%,0%,0.05)"
-    tabindex="0"
-    *ngFor="let rule of rules"
-    [rule]="rule"
-    [isSelected]="isSelected(rule)"
-    (click)="onRuleClicked(rule)"
-    (enabledChanged)="onEnabledChanged(rule, $event)">
-  </aca-rule-list-item>
+<div
+  *ngIf="inheritedRuleSetGroupingItems.length > 0"
+  class="aca-rule-list__item"
+  data-automation-id="rule-list-item"
+  [ngClass]="{ expanded: inheritedRuleSetsExpanded }">
+
+  <div class="aca-rule-list__item__header">
+
+    <div
+      tabindex="0"
+      class="aca-rule-list__item__header__title"
+      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
+      (click)="inheritedRuleSetsExpanded = !inheritedRuleSetsExpanded"
+      (keyup.enter)="inheritedRuleSetsExpanded = !inheritedRuleSetsExpanded">
+
+      <span class="aca-rule-list__item__header__title__text">
+        {{ 'ACA_FOLDER_RULES.RULE_LIST.INHERITED_RULES' | translate }}
+        <mat-icon [matTooltip]="'ACA_FOLDER_RULES.RULE_LIST.INHERITED_RULES_WILL_BE_RUN_FIRST' | translate">
+          info
+        </mat-icon>
+      </span>
+      <mat-icon class="aca-rule-list__item__header__icon">
+        {{ inheritedRuleSetsExpanded ? 'expand_more' : 'chevron_right' }}
+      </mat-icon>
+    </div>
+
+  </div>
+
+  <aca-rule-list-grouping
+    *ngIf="inheritedRuleSetsExpanded"
+    [items]="inheritedRuleSetGroupingItems"
+    [selectedRule]="selectedRule"
+    (selectRule)="onSelectRule($event)"
+    (ruleEnabledChanged)="onRuleEnabledChanged($event)"
+    (loadMoreRules)="onLoadMoreRules($event)"
+    (loadMoreRuleSets)="onLoadMoreRuleSets()">
+  </aca-rule-list-grouping>
+
+</div>
+
+<div
+  *ngIf="mainRuleSetGroupingItems.length > 0"
+  class="aca-rule-list__item"
+  data-automation-id="rule-list-item"
+  [ngClass]="{ expanded: mainRuleSetExpanded }">
+
+  <div class="aca-rule-list__item__header">
+
+    <div
+      tabindex="0"
+      class="aca-rule-list__item__header__title"
+      data-automation-id="main-rule-set-title"
+      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
+      (click)="mainRuleSetExpanded = !mainRuleSetExpanded"
+      (keyup.enter)="mainRuleSetExpanded = !mainRuleSetExpanded">
+
+      <ng-container *ngIf="isMainRuleSetOwned; else linkedRuleSet">
+        {{ 'ACA_FOLDER_RULES.RULE_LIST.OWNED_RULES' | translate }}
+      </ng-container>
+      <ng-template #linkedRuleSet>
+          {{ 'ACA_FOLDER_RULES.RULE_LIST.LINKED_RULES' | translate }}
+      </ng-template>
+
+      <mat-icon class="aca-rule-list__item__header__icon">
+        {{ mainRuleSetExpanded ? 'expand_more' : 'chevron_right' }}
+      </mat-icon>
+    </div>
+
+  </div>
+
+  <aca-rule-list-grouping
+    *ngIf="mainRuleSetExpanded"
+    [items]="mainRuleSetGroupingItems"
+    [selectedRule]="selectedRule"
+    (selectRule)="onSelectRule($event)"
+    (ruleEnabledChanged)="onRuleEnabledChanged($event)"
+    (loadMoreRules)="onLoadMoreRules($event)">
+  </aca-rule-list-grouping>
+
 </div>
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.scss
index 29824e01e..7dee1e717 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.scss
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.scss
@@ -1,4 +1,52 @@
 .aca-rule-list {
   display: flex;
   flex-direction: column;
+  overflow-y: auto;
+  gap: 8px;
+
+  &__item {
+    display: flex;
+    flex-direction: column;
+    border: 1px solid var(--theme-border-color);
+    border-radius: 12px;
+    overflow: hidden;
+
+    &__header {
+      display: flex;
+      flex-direction: row;
+      align-items: stretch;
+      cursor: pointer;
+      color: var(--theme-text-color);
+      user-select: none;
+      font-size: 0.9em;
+
+      & > * {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+      }
+
+      &__title {
+        padding: 0.5em 1em;
+        flex: 1;
+
+        &__text {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+
+          .mat-icon {
+            transform: scale(0.8);
+          }
+        }
+      }
+    }
+
+    &.expanded {
+      .aca-rule-list__item__header {
+        border-bottom: 1px solid var(--theme-border-color);
+      }
+    }
+  }
 }
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.spec.ts
index cc65d8901..f9fd3c59f 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.spec.ts
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.spec.ts
@@ -23,49 +23,50 @@
  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
  */
 
-import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RuleListUiComponent } from './rule-list.ui-component';
-import { rulesMock } from '../../mock/rules.mock';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { CoreTestingModule } from '@alfresco/adf-core';
+import { RuleListGroupingUiComponent } from '../rule-list-grouping/rule-list-grouping.ui-component';
+import { RuleListItemUiComponent } from '../rule-list-item/rule-list-item.ui-component';
+import { ownedRuleSetMock, ruleSetsMock, ruleSetWithLinkMock } from '../../mock/rule-sets.mock';
 import { DebugElement } from '@angular/core';
 import { By } from '@angular/platform-browser';
-import { CoreTestingModule } from '@alfresco/adf-core';
-import { AcaFolderRulesModule } from '@alfresco/aca-folder-rules';
+import { owningFolderIdMock } from '../../mock/node.mock';
 
 describe('RuleListUiComponent', () => {
-  let component: RuleListUiComponent;
   let fixture: ComponentFixture<RuleListUiComponent>;
+  let component: RuleListUiComponent;
   let debugElement: DebugElement;
 
+  const innerTextWithoutIcon = (element: HTMLDivElement): string => element.innerText.replace(/(expand_more|chevron_right)$/, '').trim();
+
   beforeEach(() => {
     TestBed.configureTestingModule({
-      imports: [CoreTestingModule, AcaFolderRulesModule],
-      declarations: [RuleListUiComponent]
+      imports: [CoreTestingModule],
+      declarations: [RuleListUiComponent, RuleListGroupingUiComponent, RuleListItemUiComponent]
     });
 
     fixture = TestBed.createComponent(RuleListUiComponent);
     component = fixture.componentInstance;
     debugElement = fixture.debugElement;
+
+    component.folderId = owningFolderIdMock;
+    component.inheritedRuleSets = ruleSetsMock;
   });
 
-  it('should display the list of rules', () => {
-    expect(component).toBeTruthy();
-
-    component.rules = rulesMock;
-
+  it('should show "Rules from current folder" as a title if the main rule set is owned', () => {
+    component.mainRuleSet = ownedRuleSetMock;
     fixture.detectChanges();
 
-    const rules = debugElement.queryAll(By.css('.aca-rule-list-item'));
+    const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`));
+    expect(innerTextWithoutIcon(mainRuleSetTitleElement.nativeElement as HTMLDivElement)).toBe('ACA_FOLDER_RULES.RULE_LIST.OWNED_RULES');
+  });
 
-    expect(rules).toBeTruthy('Could not find rules');
-    expect(rules.length).toBe(2, 'Unexpected number of rules');
+  it('should show "Rules from linked folder" as a title if the main rule set is linked', () => {
+    component.mainRuleSet = ruleSetWithLinkMock;
+    fixture.detectChanges();
 
-    const rule = debugElement.query(By.css('.aca-rule-list-item:first-child'));
-    const name = rule.query(By.css('.aca-rule-list-item__header__name'));
-    const description = rule.query(By.css('.aca-rule-list-item__description'));
-    const toggleBtn = rule.query(By.css('mat-slide-toggle'));
-
-    expect(name.nativeElement.textContent).toBe(rulesMock[0].name);
-    expect(toggleBtn).toBeTruthy();
-    expect(description.nativeElement.textContent).toBe(rulesMock[0].description);
+    const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`));
+    expect(innerTextWithoutIcon(mainRuleSetTitleElement.nativeElement as HTMLDivElement)).toBe('ACA_FOLDER_RULES.RULE_LIST.LINKED_RULES');
   });
 });
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.ts
index cef33e465..2318ac965 100644
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.ts
+++ b/projects/aca-folder-rules/src/lib/rule-list/rule-list/rule-list.ui-component.ts
@@ -24,35 +24,98 @@
  */
 
 import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
+import { RuleSet } from '../../model/rule-set.model';
 import { Rule } from '../../model/rule.model';
+import { RuleGroupingItem } from '../../model/rule-grouping-item.model';
+import { FolderRuleSetsService } from '../../services/folder-rule-sets.service';
 
 @Component({
   selector: 'aca-rule-list',
-  templateUrl: 'rule-list.ui-component.html',
-  styleUrls: ['rule-list.ui-component.scss'],
+  templateUrl: './rule-list.ui-component.html',
+  styleUrls: ['./rule-list.ui-component.scss'],
   encapsulation: ViewEncapsulation.None,
   host: { class: 'aca-rule-list' }
 })
 export class RuleListUiComponent {
   @Input()
-  rules: Rule[] = [];
+  mainRuleSet: RuleSet = null;
   @Input()
-  selectedRule: Rule = null;
+  folderId: string;
+  @Input()
+  inheritedRuleSets: RuleSet[] = [];
+  @Input()
+  hasMoreRuleSets = false;
+  @Input()
+  ruleSetsLoading = false;
+  @Input()
+  selectedRule = null;
 
+  @Output()
+  loadMoreRuleSets = new EventEmitter<void>();
+  @Output()
+  loadMoreRules = new EventEmitter<RuleSet>();
   @Output()
   selectRule = new EventEmitter<Rule>();
   @Output()
   ruleEnabledChanged = new EventEmitter<[Rule, boolean]>();
 
-  onRuleClicked(rule: Rule): void {
+  inheritedRuleSetsExpanded = true;
+  mainRuleSetExpanded = true;
+
+  get isMainRuleSetOwned(): boolean {
+    return FolderRuleSetsService.isOwnedRuleSet(this.mainRuleSet, this.folderId);
+  }
+
+  get mainRuleSetGroupingItems(): RuleGroupingItem[] {
+    return this.mainRuleSet ? this.getRuleSetGroupingItems(this.mainRuleSet) : [];
+  }
+
+  get inheritedRuleSetGroupingItems(): RuleGroupingItem[] {
+    const items = this.inheritedRuleSets.reduce((accumulator: RuleGroupingItem[], currentRuleSet: RuleSet) => {
+      accumulator.push(...this.getRuleSetGroupingItems(currentRuleSet));
+      return accumulator;
+    }, []);
+    if (this.ruleSetsLoading || this.hasMoreRuleSets) {
+      items.push({
+        type: this.ruleSetsLoading ? 'loading' : 'load-more-rule-sets'
+      });
+    }
+    return items;
+  }
+
+  getRuleSetGroupingItems(ruleSet: RuleSet): RuleGroupingItem[] {
+    const items: RuleGroupingItem[] = ruleSet.rules.map((rule: Rule) => ({
+      type: 'rule',
+      rule
+    }));
+    if (ruleSet.loadingRules || ruleSet.hasMoreRules) {
+      items.push(
+        ruleSet.loadingRules
+          ? {
+              type: 'loading'
+            }
+          : {
+              type: 'load-more-rules',
+              ruleSet
+            }
+      );
+    }
+    return items;
+  }
+
+  onLoadMoreRuleSets() {
+    this.loadMoreRuleSets.emit();
+  }
+
+  onLoadMoreRules(ruleSet: RuleSet) {
+    this.loadMoreRules.emit(ruleSet);
+  }
+
+  onSelectRule(rule: Rule) {
     this.selectRule.emit(rule);
   }
 
-  isSelected(rule): boolean {
-    return rule.id === this.selectedRule?.id;
-  }
-
-  onEnabledChanged(rule: Rule, isEnabled: boolean) {
-    this.ruleEnabledChanged.emit([rule, isEnabled]);
+  onRuleEnabledChanged(event: [Rule, boolean]) {
+    this.ruleEnabledChanged.emit(event);
   }
 }
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.html b/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.html
deleted file mode 100644
index c8958f66e..000000000
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.html
+++ /dev/null
@@ -1,90 +0,0 @@
-<div
-  *ngFor="let ruleSet of ruleSets"
-  class="aca-rule-set-list__item"
-  data-automation-id="rule-set-list-item"
-  [ngClass]="{ expanded: isRuleSetExpanded(ruleSet) }">
-
-  <div class="aca-rule-set-list__item__header">
-
-    <div
-      tabindex="0"
-      *ngIf="ruleSet.owningFolder.id !== folderId"
-      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
-      class="aca-rule-set-list__item__header__navigate-button"
-      (click)="clickNavigateButton(ruleSet.owningFolder)"
-      (keyup.enter)="clickNavigateButton(ruleSet.owningFolder)">
-      <mat-icon>edit_note</mat-icon>
-    </div>
-
-    <div
-      tabindex="0"
-      class="aca-rule-set-list__item__header__title"
-      data-automation-id="rule-set-item-title"
-      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
-      (click)="clickRuleSetHeader(ruleSet)"
-      (keyup.enter)="clickRuleSetHeader(ruleSet)">
-
-      <ng-container *ngIf="ruleSet.owningFolder.id === folderId; else nonOwnedRuleSet">
-        {{ 'ACA_FOLDER_RULES.RULE_LIST.OWNED_BY_THIS_FOLDER' | translate }}
-      </ng-container>
-
-      <ng-template #nonOwnedRuleSet>
-        <ng-container *ngIf="isRuleSetLinked(ruleSet); else inheritedRuleSet">
-          {{ 'ACA_FOLDER_RULES.RULE_LIST.LINKED_FROM' | translate }} {{ ruleSet.owningFolder.name }}
-        </ng-container>
-
-        <ng-template #inheritedRuleSet>
-          {{ 'ACA_FOLDER_RULES.RULE_LIST.INHERITED_FROM' | translate }} {{ ruleSet.owningFolder.name }}
-        </ng-template>
-      </ng-template>
-
-      <mat-icon class="aca-rule-set-list__item__header__icon">
-        {{ isRuleSetExpanded(ruleSet) ? 'expand_more' : 'chevron_right' }}
-      </mat-icon>
-    </div>
-
-  </div>
-
-  <ng-container *ngIf="isRuleSetExpanded(ruleSet)">
-    <aca-rule-list
-      [rules]="ruleSet.rules"
-      [selectedRule]="selectedRule"
-      (selectRule)="onSelectRule($event)"
-      (ruleEnabledChanged)="onRuleEnabledChanged($event)">
-    </aca-rule-list>
-
-    <div
-      *ngIf="ruleSet.hasMoreRules || ruleSet.loadingRules"
-      tabindex="0"
-      class="aca-rule-set-list__item__load-more load-more"
-      matRipple matRippleColor="hsla(0,0%,0%,0.05)"
-      (click)="clickLoadMoreRules(ruleSet)"
-      (keyup.enter)="clickLoadMoreRules(ruleSet)">
-      <ng-container *ngIf="!ruleSet.loadingRules; else rulesLoadingTemplate">
-        {{ 'ACA_FOLDER_RULES.RULE_LIST.LOAD_MORE_RULES' | translate }}
-      </ng-container>
-      <ng-template #rulesLoadingTemplate>
-        <mat-spinner mode="indeterminate" [diameter]="16"></mat-spinner>
-        {{ 'ACA_FOLDER_RULES.RULE_LIST.LOADING_RULES' | translate }}
-      </ng-template>
-    </div>
-  </ng-container>
-
-</div>
-
-<div
-  *ngIf="hasMoreRuleSets"
-  tabindex="0"
-  class="aca-rule-set-list__load-more load-more"
-  matRipple matRippleColor="hsla(0,0%,0%,0.05)"
-  [matRippleDisabled]="ruleSetsLoading"
-  (click)="clickLoadMoreRuleSets()"
-  (keyup.enter)="clickLoadMoreRuleSets()">
-  <ng-container *ngIf="!ruleSetsLoading; else ruleSetsLoadingTemplate">
-    {{ 'ACA_FOLDER_RULES.RULE_LIST.LOAD_MORE_RULE_SETS' | translate }}
-  </ng-container>
-  <ng-template #ruleSetsLoadingTemplate>
-    <mat-spinner mode="indeterminate" [diameter]="16"></mat-spinner>
-    {{ 'ACA_FOLDER_RULES.RULE_LIST.LOADING_RULE_SETS' | translate }}
-  </ng-template>
-</div>
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.scss b/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.scss
deleted file mode 100644
index 1f45f6210..000000000
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.scss
+++ /dev/null
@@ -1,78 +0,0 @@
-.aca-rule-set-list {
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto;
-  gap: 8px;
-
-  &__item {
-    display: flex;
-    flex-direction: column;
-    border: 1px solid var(--theme-border-color);
-    border-radius: 12px;
-    overflow: hidden;
-
-    &__header {
-      display: flex;
-      flex-direction: row;
-      align-items: stretch;
-      cursor: pointer;
-      color: var(--theme-text-color);
-      user-select: none;
-      font-size: 0.9em;
-
-      & > * {
-        display: flex;
-        flex-direction: row;
-        align-items: center;
-        justify-content: space-between;
-      }
-
-      &__title {
-        padding: 0.5em 1em;
-        flex: 1;
-      }
-
-      &__navigate-button {
-        border-right: 1px solid var(--theme-border-color);
-        padding: 0.5em;
-
-        mat-icon {
-          transform: scale(0.8);
-          transform-origin: center;
-        }
-      }
-    }
-
-    &__load-more {
-      border-top: 1px solid var(--theme-border-color);
-      padding: 0.5em 1em;
-    }
-
-    &.expanded {
-      .aca-rule-set-list__item__header {
-        border-bottom: 1px solid var(--theme-border-color);
-      }
-    }
-  }
-
-  &__load-more {
-    padding: 1em 2em;
-    border: 1px solid var(--theme-border-color);
-    border-radius: 12px;
-  }
-
-  .load-more {
-    display: flex;
-    flex-direction: row;
-    justify-content: center;
-    align-items: center;
-    color: var(--theme-disabled-text-color);
-    font-style: italic;
-    cursor: pointer;
-    text-align: center;
-
-    .mat-spinner {
-      margin-right: 0.5em;
-    }
-  }
-}
diff --git a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.spec.ts b/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.spec.ts
deleted file mode 100644
index e15ebc51a..000000000
--- a/projects/aca-folder-rules/src/lib/rule-list/rule-set-list/rule-set-list.ui-component.spec.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*!
- * @license
- * Alfresco Example Content Application
- *
- * Copyright (C) 2005 - 2020 Alfresco Software Limited
- *
- * This file is part of the Alfresco Example Content Application.
- * If the software was purchased under a paid Alfresco license, the terms of
- * the paid license agreement will prevail.  Otherwise, the software is
- * provided under the following open source license terms:
- *
- * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * The Alfresco Example Content Application is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
- */
-
-import { RuleSetListUiComponent } from './rule-set-list.ui-component';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CoreTestingModule } from '@alfresco/adf-core';
-import { RuleListUiComponent } from '../rule-list/rule-list.ui-component';
-import { RuleListItemUiComponent } from '../rule-list-item/rule-list-item.ui-component';
-import { ruleSetsMock } from '../../mock/rule-sets.mock';
-import { DebugElement } from '@angular/core';
-import { By } from '@angular/platform-browser';
-import { owningFolderIdMock } from '../../mock/node.mock';
-
-describe('RuleSetListUiComponent', () => {
-  let fixture: ComponentFixture<RuleSetListUiComponent>;
-  let component: RuleSetListUiComponent;
-  let debugElement: DebugElement;
-
-  beforeEach(() => {
-    TestBed.configureTestingModule({
-      imports: [CoreTestingModule],
-      declarations: [RuleSetListUiComponent, RuleListUiComponent, RuleListItemUiComponent]
-    });
-
-    fixture = TestBed.createComponent(RuleSetListUiComponent);
-    component = fixture.componentInstance;
-    debugElement = fixture.debugElement;
-
-    component.folderId = owningFolderIdMock;
-    component.ruleSets = ruleSetsMock;
-    fixture.detectChanges();
-  });
-
-  it('should display a list of rule sets', () => {
-    const ruleSetElements = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
-
-    expect(ruleSetElements.length).toBe(3);
-  });
-
-  it('should show the right message for the right sort of rule set', () => {
-    const ruleSetTitleElements = debugElement.queryAll(By.css(`[data-automation-id="rule-set-item-title"]`));
-
-    const innerTextWithoutIcon = (element: HTMLDivElement): string => element.innerText.replace(/(expand_more|chevron_right)$/, '').trim();
-
-    expect(ruleSetTitleElements.length).toBe(3);
-    expect(innerTextWithoutIcon(ruleSetTitleElements[0].nativeElement as HTMLDivElement)).toBe(
-      'ACA_FOLDER_RULES.RULE_LIST.INHERITED_FROM other-folder-name'
-    );
-    expect(innerTextWithoutIcon(ruleSetTitleElements[1].nativeElement as HTMLDivElement)).toBe('ACA_FOLDER_RULES.RULE_LIST.OWNED_BY_THIS_FOLDER');
-    expect(innerTextWithoutIcon(ruleSetTitleElements[2].nativeElement as HTMLDivElement)).toBe(
-      'ACA_FOLDER_RULES.RULE_LIST.LINKED_FROM other-folder-name'
-    );
-  });
-});
diff --git a/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.spec.ts b/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.spec.ts
index e91cfc5a3..5a4bf07be 100644
--- a/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.spec.ts
+++ b/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.spec.ts
@@ -30,7 +30,7 @@ import { FolderRulesService } from './folder-rules.service';
 import { ContentApiService } from '@alfresco/aca-shared';
 import { getOtherFolderEntryMock, getOwningFolderEntryMock, otherFolderIdMock, owningFolderIdMock, owningFolderMock } from '../mock/node.mock';
 import { of } from 'rxjs';
-import { getRuleSetsResponseMock, ruleSetsMock } from '../mock/rule-sets.mock';
+import { getDefaultRuleSetResponseMock, getRuleSetsResponseMock, inheritedRuleSetMock, ownedRuleSetMock } from '../mock/rule-sets.mock';
 import { take } from 'rxjs/operators';
 import { inheritedRulesMock, linkedRulesMock, ownedRulesMock, ruleMock } from '../mock/rules.mock';
 
@@ -52,7 +52,11 @@ describe('FolderRuleSetsService', () => {
     folderRulesService = TestBed.inject(FolderRulesService);
     contentApiService = TestBed.inject(ContentApiService);
 
-    callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi');
+    callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi')
+      .withArgs(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')
+      .and.returnValue(of(getDefaultRuleSetResponseMock))
+      .withArgs(`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET')
+      .and.returnValue(of(getRuleSetsResponseMock));
     spyOn<any>(folderRulesService, 'getRules')
       .withArgs(jasmine.anything(), 'rule-set-no-links')
       .and.returnValue(of({ rules: ownedRulesMock, hasMoreRules: false }))
@@ -69,7 +73,6 @@ describe('FolderRuleSetsService', () => {
   });
 
   it(`should load node info when loading the node's rule sets`, async () => {
-    callApiSpy.and.returnValue(of(getRuleSetsResponseMock));
     // take(2), because: 1 = init of the BehaviourSubject, 2 = in subscribe
     const folderInfoPromise = folderRuleSetsService.folderInfo$.pipe(take(2)).toPromise();
 
@@ -80,29 +83,38 @@ describe('FolderRuleSetsService', () => {
     expect(folderInfo).toEqual(owningFolderMock);
   });
 
-  it('should load rule sets of a node', async () => {
-    callApiSpy.and.returnValue(of(getRuleSetsResponseMock));
+  it('should load the main rule set (main or linked)', async () => {
     // take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
-    const ruleSetListingPromise = folderRuleSetsService.ruleSetListing$.pipe(take(3)).toPromise();
+    const mainRuleSetPromise = folderRuleSetsService.mainRuleSet$.pipe(take(3)).toPromise();
+
+    folderRuleSetsService.loadRuleSets(owningFolderIdMock);
+    const ruleSet = await mainRuleSetPromise;
+
+    expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET');
+    expect(ruleSet).toEqual(ownedRuleSetMock);
+  });
+
+  it('should load inherited rule sets of a node and filter out owned or inherited rule sets', async () => {
+    // take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
+    const inheritedRuleSetsPromise = folderRuleSetsService.inheritedRuleSets$.pipe(take(3)).toPromise();
     const hasMoreRuleSetsPromise = folderRuleSetsService.hasMoreRuleSets$.pipe(take(3)).toPromise();
 
     folderRuleSetsService.loadRuleSets(owningFolderIdMock);
-    const ruleSets = await ruleSetListingPromise;
+    const ruleSets = await inheritedRuleSetsPromise;
     const hasMoreRuleSets = await hasMoreRuleSetsPromise;
 
     expect(callApiSpy).toHaveBeenCalledWith(
       `/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`,
       'GET'
     );
-    expect(ruleSets).toEqual(ruleSetsMock);
+    expect(ruleSets).toEqual([inheritedRuleSetMock]);
     expect(hasMoreRuleSets).toEqual(false);
   });
 
   it('should select the first rule of the owned rule set of the folder', async () => {
-    callApiSpy.and.returnValue(of(getRuleSetsResponseMock));
     const selectRuleSpy = spyOn(folderRulesService, 'selectRule');
     // take(3), because: 1 = init of the BehaviourSubject, 2 = reinitialise at beginning of loadRuleSets, 3 = in subscribe
-    const ruleSetListingPromise = folderRuleSetsService.ruleSetListing$.pipe(take(3)).toPromise();
+    const ruleSetListingPromise = folderRuleSetsService.inheritedRuleSets$.pipe(take(3)).toPromise();
 
     folderRuleSetsService.loadRuleSets(owningFolderIdMock);
     await ruleSetListingPromise;
diff --git a/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.ts b/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.ts
index aca2451d9..5b8034973 100644
--- a/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.ts
+++ b/projects/aca-folder-rules/src/lib/services/folder-rule-sets.service.ts
@@ -40,20 +40,46 @@ import { Rule } from '../model/rule.model';
 export class FolderRuleSetsService {
   public static MAX_RULE_SETS_PER_GET = 100;
 
+  static isOwnedRuleSet(ruleSet: RuleSet, nodeId: string): boolean {
+    return ruleSet.owningFolder.id === nodeId;
+  }
+  static isLinkedRuleSet(ruleSet: RuleSet, nodeId: string): boolean {
+    return ruleSet.linkedToBy.indexOf(nodeId) > -1;
+  }
+  static isMainRuleSet(ruleSet: RuleSet, nodeId: string): boolean {
+    return this.isOwnedRuleSet(ruleSet, nodeId) || this.isLinkedRuleSet(ruleSet, nodeId);
+  }
+  static isInheritedRuleSet(ruleSet: RuleSet, nodeId: string): boolean {
+    return !this.isMainRuleSet(ruleSet, nodeId);
+  }
+
   private currentFolder: NodeInfo = null;
-  private ruleSets: RuleSet[] = [];
+  private mainRuleSet: RuleSet = null;
+  private inheritedRuleSets: RuleSet[] = [];
   private hasMoreRuleSets = true;
 
-  private ruleSetListingSource = new BehaviorSubject<RuleSet[]>([]);
+  private mainRuleSetSource = new BehaviorSubject<RuleSet>(null);
+  private inheritedRuleSetsSource = new BehaviorSubject<RuleSet[]>([]);
   private hasMoreRuleSetsSource = new BehaviorSubject<boolean>(true);
   private folderInfoSource = new BehaviorSubject<NodeInfo>(null);
   private isLoadingSource = new BehaviorSubject<boolean>(false);
 
-  ruleSetListing$: Observable<RuleSet[]> = this.ruleSetListingSource.asObservable();
+  mainRuleSet$: Observable<RuleSet> = this.mainRuleSetSource.asObservable();
+  inheritedRuleSets$: Observable<RuleSet[]> = this.inheritedRuleSetsSource.asObservable();
   hasMoreRuleSets$: Observable<boolean> = this.hasMoreRuleSetsSource.asObservable();
   folderInfo$: Observable<NodeInfo> = this.folderInfoSource.asObservable();
   isLoading$ = this.isLoadingSource.asObservable();
 
+  selectedRuleSet$ = this.folderRulesService.selectedRule$.pipe(
+    map((rule: Rule) => {
+      if (this.mainRuleSet?.rules.findIndex((r: Rule) => r.id === rule.id) > -1) {
+        return this.mainRuleSet;
+      } else {
+        return this.inheritedRuleSets.find((ruleSet: RuleSet) => ruleSet.rules.findIndex((r: Rule) => r.id === rule.id) > -1) ?? null;
+      }
+    })
+  );
+
   constructor(private apiService: AlfrescoApiService, private contentApi: ContentApiService, private folderRulesService: FolderRulesService) {}
 
   private callApi(path: string, httpMethod: string, body: object = {}): Promise<any> {
@@ -62,7 +88,19 @@ export class FolderRuleSetsService {
     return this.apiService.getInstance().contentPrivateClient.callApi(path, httpMethod, ...params);
   }
 
-  private getRuleSets(nodeId: string, skipCount = 0): Observable<RuleSet[]> {
+  private getMainRuleSet(nodeId: string): Observable<RuleSet> {
+    return from(this.callApi(`/nodes/${nodeId}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')).pipe(
+      catchError((error) => {
+        if (error.status === 404) {
+          return of({ entry: null });
+        }
+        return of(error);
+      }),
+      switchMap((res) => this.formatRuleSet(res.entry))
+    );
+  }
+
+  private getInheritedRuleSets(nodeId: string, skipCount = 0): Observable<RuleSet[]> {
     return from(
       this.callApi(
         `/nodes/${nodeId}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=${skipCount}&maxItems=${FolderRuleSetsService.MAX_RULE_SETS_PER_GET}`,
@@ -74,16 +112,19 @@ export class FolderRuleSetsService {
           this.hasMoreRuleSets = res.list.pagination.hasMoreItems;
         }
       }),
-      switchMap((res) => this.formatRuleSets(res))
+      switchMap((res) => this.formatRuleSets(res)),
+      map((ruleSets: RuleSet[]) => ruleSets.filter((ruleSet) => FolderRuleSetsService.isInheritedRuleSet(ruleSet, this.currentFolder.id)))
     );
   }
 
   loadRuleSets(nodeId: string) {
     this.isLoadingSource.next(true);
-    this.ruleSets = [];
+    this.mainRuleSet = null;
+    this.inheritedRuleSets = [];
     this.hasMoreRuleSets = true;
     this.currentFolder = null;
-    this.ruleSetListingSource.next(this.ruleSets);
+    this.mainRuleSetSource.next(this.mainRuleSet);
+    this.inheritedRuleSetsSource.next(this.inheritedRuleSets);
     this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
     this.getNodeInfo(nodeId)
       .pipe(
@@ -91,29 +132,27 @@ export class FolderRuleSetsService {
           this.currentFolder = nodeInfo;
           this.folderInfoSource.next(this.currentFolder);
         }),
-        switchMap(() => this.getRuleSets(nodeId)),
+        switchMap(() => combineLatest(this.getMainRuleSet(nodeId), this.getInheritedRuleSets(nodeId))),
         finalize(() => this.isLoadingSource.next(false))
       )
-      .subscribe((ruleSets: RuleSet[]) => {
-        this.ruleSets = ruleSets;
-        this.ruleSetListingSource.next(this.ruleSets);
+      .subscribe(([mainRuleSet, inheritedRuleSets]) => {
+        this.mainRuleSet = mainRuleSet;
+        this.inheritedRuleSets = inheritedRuleSets;
+        this.mainRuleSetSource.next(mainRuleSet);
+        this.inheritedRuleSetsSource.next(inheritedRuleSets);
         this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
-        this.folderRulesService.selectRule(this.getOwnedOrLinkedRuleSet()?.rules[0] ?? ruleSets[0]?.rules[0]);
+        this.folderRulesService.selectRule(mainRuleSet?.rules[0] ?? inheritedRuleSets[0]?.rules[0] ?? null);
       });
   }
 
-  loadMoreRuleSets(selectLastRule = false) {
+  loadMoreInheritedRuleSets() {
     this.isLoadingSource.next(true);
-    this.getRuleSets(this.currentFolder.id, this.ruleSets.length)
+    this.getInheritedRuleSets(this.currentFolder.id, this.inheritedRuleSets.length)
       .pipe(finalize(() => this.isLoadingSource.next(false)))
       .subscribe((ruleSets) => {
-        this.ruleSets.push(...ruleSets);
-        this.ruleSetListingSource.next(this.ruleSets);
+        this.inheritedRuleSets.push(...ruleSets);
+        this.inheritedRuleSetsSource.next(this.inheritedRuleSets);
         this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
-        if (selectLastRule) {
-          const ownedRuleSet = this.getOwnedOrLinkedRuleSet();
-          this.folderRulesService.selectRule(ownedRuleSet?.rules[ownedRuleSet.rules.length - 1]);
-        }
       });
   }
 
@@ -140,6 +179,9 @@ export class FolderRuleSetsService {
   }
 
   private formatRuleSet(entry: any): Observable<RuleSet> {
+    if (!entry) {
+      return of(null);
+    }
     return combineLatest(
       this.currentFolder?.id === entry.owningFolder ? of(this.currentFolder) : this.getNodeInfo(entry.owningFolder || ''),
       this.folderRulesService.getRules(entry.owningFolder || '', entry.id)
@@ -156,15 +198,38 @@ export class FolderRuleSetsService {
     );
   }
 
-  getRuleSetFromRuleId(ruleId: string): RuleSet {
-    return this.ruleSets.find((ruleSet: RuleSet) => ruleSet.rules.findIndex((r: Rule) => r.id === ruleId) > -1) ?? null;
+  removeRuleFromMainRuleSet(ruleId: string) {
+    if (this.mainRuleSet) {
+      const index = this.mainRuleSet.rules.findIndex((rule: Rule) => rule.id === ruleId);
+      if (index > -1) {
+        if (this.mainRuleSet.rules.length > 1) {
+          this.mainRuleSet.rules.splice(index, 1);
+        } else {
+          this.mainRuleSet = null;
+          this.mainRuleSetSource.next(this.mainRuleSet);
+        }
+      }
+    }
   }
 
-  getOwnedOrLinkedRuleSet(): RuleSet {
-    return (
-      this.ruleSets.find(
-        (ruleSet: RuleSet) => ruleSet.owningFolder.id === this.currentFolder.id || ruleSet.linkedToBy.indexOf(this.currentFolder.id) > -1
-      ) ?? null
-    );
+  addOrUpdateRuleInMainRuleSet(newRule: Rule) {
+    if (this.mainRuleSet) {
+      const index = this.mainRuleSet.rules.findIndex((rule: Rule) => rule.id === newRule.id);
+      if (index > -1) {
+        this.mainRuleSet.rules.splice(index, 1, newRule);
+      } else {
+        this.mainRuleSet.rules.push(newRule);
+      }
+      this.folderRulesService.selectRule(newRule);
+    } else {
+      this.getMainRuleSet(this.currentFolder.id).subscribe((mainRuleSet: RuleSet) => {
+        this.mainRuleSet = mainRuleSet;
+        this.mainRuleSetSource.next(mainRuleSet);
+        if (mainRuleSet) {
+          const ruleToSelect = mainRuleSet.rules.find((rule: Rule) => rule.id === newRule.id);
+          this.folderRulesService.selectRule(ruleToSelect);
+        }
+      });
+    }
   }
 }
diff --git a/projects/aca-folder-rules/src/lib/services/folder-rules.service.spec.ts b/projects/aca-folder-rules/src/lib/services/folder-rules.service.spec.ts
index bdf28deac..8becc0f80 100644
--- a/projects/aca-folder-rules/src/lib/services/folder-rules.service.spec.ts
+++ b/projects/aca-folder-rules/src/lib/services/folder-rules.service.spec.ts
@@ -41,6 +41,8 @@ describe('FolderRulesService', () => {
   const nodeId = owningFolderIdMock;
   const ruleSetId = 'rule-set-id';
   const mockedRule = ruleMock('rule-mock');
+  const { id, ...mockedRuleWithoutId } = mockedRule;
+  const mockedRuleEntry = { entry: mockedRule };
   const ruleId = mockedRule.id;
 
   beforeEach(() => {
@@ -120,14 +122,18 @@ describe('FolderRulesService', () => {
   });
 
   it('should send correct POST request and return created rule', async () => {
-    callApiSpy.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`, 'POST', mockedRule).and.returnValue(Promise.resolve(mockedRule));
-    const result = await folderRulesService.createRule(nodeId, mockedRule, ruleSetId);
+    callApiSpy
+      .withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`, 'POST', mockedRuleWithoutId)
+      .and.returnValue(Promise.resolve(mockedRuleEntry));
+    const result = await folderRulesService.createRule(nodeId, mockedRuleWithoutId, ruleSetId);
 
     expect(result).toEqual(mockedRule);
   });
 
   it('should send correct PUT request to update rule and return it', async () => {
-    callApiSpy.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', mockedRule).and.returnValue(Promise.resolve(mockedRule));
+    callApiSpy
+      .withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', mockedRule)
+      .and.returnValue(Promise.resolve(mockedRuleEntry));
 
     const result = await folderRulesService.updateRule(nodeId, ruleId, mockedRule, ruleSetId);
     expect(result).toEqual(mockedRule);
diff --git a/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts b/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts
index eefe36785..dafa7d365 100644
--- a/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts
+++ b/projects/aca-folder-rules/src/lib/services/folder-rules.service.ts
@@ -126,23 +126,19 @@ export class FolderRulesService {
           ruleSet.hasMoreRules = res.hasMoreRules;
           ruleSet.rules.splice(skipCount);
           ruleSet.rules.push(...res.rules);
-          if (selectRule === 'first') {
-            this.selectRule(ruleSet.rules[0]);
-          } else if (selectRule === 'last') {
-            this.selectRule(ruleSet.rules[ruleSet.rules.length - 1]);
-          } else if (selectRule) {
-            this.selectRule(selectRule);
-          }
+          this.selectRuleInRuleSet(ruleSet, selectRule);
         });
     }
   }
 
-  createRule(nodeId: string, rule: Partial<Rule>, ruleSetId: string): Promise<unknown> {
-    return this.callApi(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`, 'POST', { ...rule });
+  async createRule(nodeId: string, rule: Partial<Rule>, ruleSetId: string = '-default-'): Promise<Rule> {
+    const response = await this.callApi(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules`, 'POST', { ...rule });
+    return this.formatRule(response.entry);
   }
 
-  updateRule(nodeId: string, ruleId: string, rule: Rule, ruleSetId: string): Promise<unknown> {
-    return this.callApi(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', { ...rule });
+  async updateRule(nodeId: string, ruleId: string, rule: Rule, ruleSetId: string = '-default-'): Promise<Rule> {
+    const response = await this.callApi(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'PUT', { ...rule });
+    return this.formatRule(response.entry);
   }
 
   deleteRule(nodeId: string, ruleId: string, ruleSetId: string = '-default-') {
@@ -207,4 +203,14 @@ export class FolderRulesService {
   selectRule(rule: Rule) {
     this.selectedRuleSource.next(rule);
   }
+
+  selectRuleInRuleSet(ruleSet: RuleSet, selectRule: 'first' | 'last' | Rule = null) {
+    if (selectRule === 'first') {
+      this.selectRule(ruleSet.rules[0]);
+    } else if (selectRule === 'last') {
+      this.selectRule(ruleSet.rules[ruleSet.rules.length - 1]);
+    } else if (selectRule) {
+      this.selectRule(selectRule);
+    }
+  }
 }