[ACS-4538] Make folder-rules secondary entry point of aca-content (#3181)

* [ACS-4538] Make folder-rules secondary entry point of aca-content

* [ACA-4538] Fix folder rules imports

* [ACS-4538] Fix folder-rules unit tests

* [ACS-4538] Fix package.json after folder rules transition

* [ACS-4538] Remove duplicated peerDependency

* [ACS-4538] Import fix
This commit is contained in:
MichalKinas
2023-06-16 14:55:04 +02:00
committed by GitHub
parent 9916ecb98b
commit e0a74d7fb9
122 changed files with 111 additions and 296 deletions
@@ -0,0 +1,71 @@
{
"$schema": "../../../../extension.schema.json",
"$id": "0455ca6c-cc7a-43ae-bbf7-35795413d2dd",
"$name": "Folder Rules Plugin",
"$version": "0.0.1",
"$vendor": "Hyland Software, Inc. and its affiliates",
"$license": "LGPL-3.0",
"features": {
"toolbar": [
{
"id": "app.toolbar.more",
"children": [
{
"id": "app.toolbar.rules.separator",
"type": "separator",
"rules": {
"visible": "app.selection.folder"
}
},
{
"id": "app.toolbar.rules.create",
"title": "ACA_FOLDER_RULES.MENU.CREATE_RULES",
"description": "ACA_FOLDER_RULES.MENU.CREATE_RULES_DESC",
"icon": "add",
"rules": {
"visible": "rules.canCreateFolderRule"
}
},
{
"id": "app.toolbar.rules.link",
"title": "ACA_FOLDER_RULES.MENU.LINK_RULES",
"description": "ACA_FOLDER_RULES.MENU.LINK_RULES_DESC",
"icon": "link",
"rules": {
"visible": "rules.canLinkFolderRule"
}
}
]
}
],
"contextMenu": [
{
"id": "app.toolbar.rules.separator",
"type": "separator",
"rules": {
"visible": "app.selection.folder"
}
},
{
"id": "app.toolbar.rules.create",
"title": "ACA_FOLDER_RULES.MENU.CREATE_RULES",
"description": "ACA_FOLDER_RULES.MENU.CREATE_RULES_DESC",
"icon": "add",
"rules": {
"visible": "rules.canCreateFolderRule"
}
},
{
"id": "app.toolbar.rules.link",
"title": "ACA_FOLDER_RULES.MENU.LINK_RULES",
"description": "ACA_FOLDER_RULES.MENU.LINK_RULES_DESC",
"icon": "link",
"rules": {
"visible": "rules.canLinkFolderRule"
}
}
]
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "إدارة القواعد"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "إلغاء",
"CREATE": "إنشاء",
"CREATE_TITLE": "إنشاء قاعدة",
"UPDATE": "تحديث",
"UPDATE_TITLE": "تحرير قاعدة"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "الاسم",
"DESCRIPTION": "الوصف",
"WHEN": "متي",
"PERFORM_ACTIONS": "نفذ الإجراءات",
"OPTIONS": "خيارات أخرى"
},
"PLACEHOLDER": {
"NAME": "أدخل اسم للقاعدة",
"DESCRIPTION": "أدخل وصف للقاعدة",
"NO_DESCRIPTION": "لا يوجد وصف",
"VALUE": "القيمة",
"CHOOSE_FOLDER": "اختر مجلد الوجهة"
},
"ERROR": {
"REQUIRED": "هذا الحقل مطلوب",
"RULE_COMPOSITE_CONDITION_INVALID": "مجموعة شرطية واحدة أو أكثر فارغة",
"INSUFFICIENT_TRIGGERS_SELECTED": "مطلوب مشغل واحد على الأقل"
},
"TRIGGERS": {
"INBOUND": "يتم إنشاء العناصر أو الدخول إلى هذا المجلد",
"UPDATE": "تم تحديث العناصر",
"OUTBOUND": "تم حذف العناصر أو ترك هذا المجلد"
},
"OPTIONS": {
"IS_INHERITABLE": "تنطبق القاعدة على المجلدات الفرعية",
"IS_ASYNCHRONOUS": "تشغيل القاعدة في الخلفية",
"DISABLE_RULE": "تعطيل القاعدة",
"ERROR_SCRIPT": "في حالة حدوث أخطاء قم بتشغيل البرنامج النصي",
"NO_SCRIPT": "بلا"
},
"COMPARATORS": {
"EQUALS": "(=) يساوي",
"CONTAINS": "يتضمن",
"STARTS_WITH": "يبدأ بـ",
"ENDS_WITH": "ينتهي بـ",
"GREATER_THAN": "(>) أكبر من",
"LESS_THAN": "(<) أقل من",
"GREATER_THAN_OR_EQUAL": "(>=) أكبر من أو يساوي",
"LESS_THAN_OR_EQUAL": "(<=) أقل من أو يساوي",
"ON": "(=) في",
"AFTER": "(>) بعد",
"BEFORE": "(<) قبل",
"ON_OR_AFTER": "(>=) في أو بعد",
"ON_OR_BEFORE": "(<=) في أو قبل",
"INSTANCE_OF": "(=) هو"
},
"FIELDS": {
"NAME": "الاسم",
"SIZE": "الحجم",
"MIMETYPE": "نوع Mime",
"ENCODING": "ترميز",
"HAS_CATEGORY": "لديه فئة",
"HAS_TAG": "له علامة",
"HAS_ASPECT": "له جانب"
},
"LOGIC_OPERATORS": {
"IF": "إذا",
"NOT_IF": "ليس إذا",
"AND": "و",
"OR": "أو"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "أضف الشرط",
"ADD_GROUP": "أضف مجموعة شرطية",
"REMOVE": "إزالة"
},
"NO_CONDITIONS": "لا شروط",
"NO_CONDITIONS_IN_GROUP": "لا توجد شروط في المجموعة",
"ACTION_BUTTONS": {
"ADD_ACTION": "إضافة إجراء",
"REMOVE": "إزالة"
},
"ACTION_SELECT_PLACEHOLDER": "حدد إجراء"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "قواعد"
},
"ACTIONS": {
"CREATE_RULE": "إنشاء قاعدة",
"LINK_RULES": "قواعد الارتباط",
"EDIT_RULE": "تحرير",
"SEE_IN_FOLDER": "انظر في المجلد",
"INHERIT_RULES": "ترث القواعد"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "القائمة فارغة",
"SUBTITLE": "لا توجد قواعد محددة لهذا المجلد حتى الآن."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "حذف القاعدة",
"MESSAGE": "هل انت متأكد من رغبتك بحذف هذه القاعدة؟"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "حذف ارتباط مجموعة القواعد",
"MESSAGE": "هل أنت متأكد أنك تريد حذف الارتباط لمجموعة القواعد هذه؟"
}
},
"RULE_LIST": {
"OWNED_RULES": "القواعد من المجلد الحالي",
"LINKED_RULES": "قواعد من مجلد مرتبط",
"INHERITED_RULES": "القواعد الموروثة",
"LOAD_MORE_RULE_SETS": "قواعد التحميل من المجلدات الأخرى",
"LOAD_MORE_RULES": "تحميل المزيد من القواعد",
"LOADING_RULES": "قواعد التحميل",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "سيتم تشغيل القواعد الموروثة أولاً",
"ALL_LINKED_RULES_ARE_DISABLED": "تم تعطيل جميع القواعد المرتبطة من مجموعة القواعد هذه"
},
"LINK_RULES_DIALOG": {
"TITLE": "حدد مجلدًا لربط القواعد منه",
"CANCEL": "إلغاء",
"SUBMIT": "حدد مجلد",
"EMPTY_RULES_LIST": {
"TITLE": "لا توجد قواعد مملوكة",
"SUBTITLE": "لا يحتوي المجلد المحدد على أي قواعد خاصة به."
},
"LIST_OF_RULES_TO_LINK": "قائمة القواعد التي سيتم ربطها",
"ERRORS": {
"REQUEST_FAILED": "حدث خطأ أثناء محاولة إنشاء ارتباط لمجموعة قواعد"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "خطأ أثناء محاولة حذف ارتباط من مجموعة القواعد"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Správa pravidel"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Zrušit",
"CREATE": "Vytvořit",
"CREATE_TITLE": "Vytvořit pravidlo",
"UPDATE": "Aktualizovat",
"UPDATE_TITLE": "Edit a rule"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Název",
"DESCRIPTION": "Popis",
"WHEN": "Když",
"PERFORM_ACTIONS": "Perform actions",
"OPTIONS": "Other options"
},
"PLACEHOLDER": {
"NAME": "Zadejte název pravidla",
"DESCRIPTION": "Zadejte popis pravidla",
"NO_DESCRIPTION": "Žádný popis",
"VALUE": "Hodnota",
"CHOOSE_FOLDER": "Vyberte cílovou složku"
},
"ERROR": {
"REQUIRED": "Toto pole je povinné",
"RULE_COMPOSITE_CONDITION_INVALID": "Nejméně jedna skupina podmínek je prázdná",
"INSUFFICIENT_TRIGGERS_SELECTED": "Je požadován alespoň jeden spouštěč"
},
"TRIGGERS": {
"INBOUND": "Položky jsou vytvořeny nebo vstupují do této složky",
"UPDATE": "Položky jsou aktualizovány",
"OUTBOUND": "Položky jsou odstraněny nebo opouští tuto složku"
},
"OPTIONS": {
"IS_INHERITABLE": "Rule applies to subfolders",
"IS_ASYNCHRONOUS": "Run rule in the background",
"DISABLE_RULE": "Disable rule",
"ERROR_SCRIPT": "If errors occur run script",
"NO_SCRIPT": "Žádný"
},
"COMPARATORS": {
"EQUALS": "(=) Rovná se",
"CONTAINS": "Obsahuje",
"STARTS_WITH": "Začíná na",
"ENDS_WITH": "Končí na",
"GREATER_THAN": "(>) Větší než",
"LESS_THAN": "(<) Menší než",
"GREATER_THAN_OR_EQUAL": "(>=) Větší než nebo rovno",
"LESS_THAN_OR_EQUAL": "(<=) Menší než nebo rovno",
"ON": "(=) Na",
"AFTER": "(>) Po",
"BEFORE": "(<) Před",
"ON_OR_AFTER": "(>=) Na nebo po",
"ON_OR_BEFORE": "(<=) Na nebo před",
"INSTANCE_OF": "(=) Je"
},
"FIELDS": {
"NAME": "Název",
"SIZE": "Velikost",
"MIMETYPE": "Typ MIME",
"ENCODING": "Kódování",
"HAS_CATEGORY": "Má kategorii",
"HAS_TAG": "Má značku",
"HAS_ASPECT": "Má aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Pokud",
"NOT_IF": "NE pokud",
"AND": "A",
"OR": "Nebo"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Add condition",
"ADD_GROUP": "Add condition group",
"REMOVE": "Odebrat"
},
"NO_CONDITIONS": "Žádné podmínky",
"NO_CONDITIONS_IN_GROUP": "Žádné podmínky ve skupině",
"ACTION_BUTTONS": {
"ADD_ACTION": "Přidat akci",
"REMOVE": "Odebrat"
},
"ACTION_SELECT_PLACEHOLDER": "Select an action"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "pravidla"
},
"ACTIONS": {
"CREATE_RULE": "Vytvořit pravidlo",
"LINK_RULES": "Pravidla pro odkazy",
"EDIT_RULE": "Upravit",
"SEE_IN_FOLDER": "Zobrazit ve složce",
"INHERIT_RULES": "Zděděná pravidla"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Tento seznam je prázdný",
"SUBTITLE": "Pro tuto složku ještě nejsou definována žádná pravidla."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Odstranit pravidlo",
"MESSAGE": "Chcete skutečně smazat toto pravidlo?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Smazat odkaz na nastavené pravidlo",
"MESSAGE": "Chcete skutečně smazat odkaz na toto nastavené pravidlo?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Pravidla z aktuální složky",
"LINKED_RULES": "Pravidla z odkazované složky",
"INHERITED_RULES": "Zděděná pravidla",
"LOAD_MORE_RULE_SETS": "Načíst pravidla z jiných složek",
"LOAD_MORE_RULES": "Načíst více pravidel",
"LOADING_RULES": "Načítání pravidel",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Jako první budou spuštěna zděděná pravidla",
"ALL_LINKED_RULES_ARE_DISABLED": "Všechna pravidla, na která byl vytvořen odkaz z tohoto souboru pravidel, jsou zakázaná"
},
"LINK_RULES_DIALOG": {
"TITLE": "Vyberte složku pro vytvoření odkazu na pravidla z",
"CANCEL": "Zrušit",
"SUBMIT": "Vybrat složku",
"EMPTY_RULES_LIST": {
"TITLE": "Žádná vlastní pravidla",
"SUBTITLE": "Vybraná složka neobsahuje žádná vlastní pravidla."
},
"LIST_OF_RULES_TO_LINK": "Seznam pravidel, na která bude vytvořen odkaz",
"ERRORS": {
"REQUEST_FAILED": "Vyskytla se chyba při vytváření odkaz na nastavené pravidlo"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Vyskytla se chyba při pokusu o smazání odkazu z nastaveného pravidla"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Administrér regler"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Annuller",
"CREATE": "Opret",
"CREATE_TITLE": "Opret en regel",
"UPDATE": "Opdater",
"UPDATE_TITLE": "Redigér en regel"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Navn",
"DESCRIPTION": "Beskrivelse",
"WHEN": "Når",
"PERFORM_ACTIONS": "Udfør handlinger",
"OPTIONS": "Andre optioner"
},
"PLACEHOLDER": {
"NAME": "Indtast regelnavn",
"DESCRIPTION": "Indtast regelbeskrivelse",
"NO_DESCRIPTION": "Ingen beskrivelse",
"VALUE": "Værdi",
"CHOOSE_FOLDER": "Vælg destinationsmappe"
},
"ERROR": {
"REQUIRED": "Dette felt er påkrævet",
"RULE_COMPOSITE_CONDITION_INVALID": "En eller flere betingelsesgrupper tomme",
"INSUFFICIENT_TRIGGERS_SELECTED": "Der kræves mindst en udløser"
},
"TRIGGERS": {
"INBOUND": "Der er oprettet elementer, eller indtast denne mappe",
"UPDATE": "Elementer er opdateret",
"OUTBOUND": "Elementer slettes, eller forlad denne mappe"
},
"OPTIONS": {
"IS_INHERITABLE": "Regel gælder for undermapper",
"IS_ASYNCHRONOUS": "Kør regel i baggrunden",
"DISABLE_RULE": "Deaktivér regel",
"ERROR_SCRIPT": "Kør script, hvis der forekommer fejl",
"NO_SCRIPT": "Ingen"
},
"COMPARATORS": {
"EQUALS": "(=) lig med",
"CONTAINS": "Indeholder",
"STARTS_WITH": "Starter med",
"ENDS_WITH": "Slutter med",
"GREATER_THAN": "(>) større end",
"LESS_THAN": "(<) mindre end",
"GREATER_THAN_OR_EQUAL": "(>=) større end eller lig med",
"LESS_THAN_OR_EQUAL": "(<=) mindre end eller lig med",
"ON": "(=) ved",
"AFTER": "(>) efter",
"BEFORE": "(<) før",
"ON_OR_AFTER": "(>=) ved eller efter",
"ON_OR_BEFORE": "(<=) ved eller før",
"INSTANCE_OF": "(=) er"
},
"FIELDS": {
"NAME": "Navn",
"SIZE": "Størrelse",
"MIMETYPE": "Mimetype",
"ENCODING": "Kodning",
"HAS_CATEGORY": "Har kategori",
"HAS_TAG": "Har tag",
"HAS_ASPECT": "Har aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Hvis",
"NOT_IF": "IKKE hvis",
"AND": "Og",
"OR": "Eller"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Tilføj betingelse",
"ADD_GROUP": "Tilføj betingelsesgruppe",
"REMOVE": "Fjern"
},
"NO_CONDITIONS": "Ingen betingelser",
"NO_CONDITIONS_IN_GROUP": "Ingen betingelser i denne gruppe",
"ACTION_BUTTONS": {
"ADD_ACTION": "Tilføj handling",
"REMOVE": "Fjern"
},
"ACTION_SELECT_PLACEHOLDER": "Vælg en handling"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regler"
},
"ACTIONS": {
"CREATE_RULE": "Opret en regel",
"LINK_RULES": "Forbind regler",
"EDIT_RULE": "Rediger",
"SEE_IN_FOLDER": "Se i mappe",
"INHERIT_RULES": "Overtag regler"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Listen er tom",
"SUBTITLE": "Der er ingen definerede regler for denne mappe endnu."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Slet regel",
"MESSAGE": "Er du sikker på, at du vil slette denne regel?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Slet regelsætforbindelse",
"MESSAGE": "Er du sikker på, at du vil slette forbindelsen til dette regelsæt?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regler fra aktuel mappe",
"LINKED_RULES": "Regler fra forbundet mappe",
"INHERITED_RULES": "Overtagede regler",
"LOAD_MORE_RULE_SETS": "Indlæs regler fra andre mapper",
"LOAD_MORE_RULES": "Indlæs flere regler",
"LOADING_RULES": "Indlæser regler",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Overtagede regler køres først",
"ALL_LINKED_RULES_ARE_DISABLED": "Alle regler, der er forbundet af dette indstillede regelsæt, er deaktiveret"
},
"LINK_RULES_DIALOG": {
"TITLE": "Vælg en mappe for at forbinde regler fra",
"CANCEL": "Annuller",
"SUBMIT": "Vælg mappe",
"EMPTY_RULES_LIST": {
"TITLE": "Ingen egne regler",
"SUBTITLE": "Den valgte mappe har ikke sine egne regler."
},
"LIST_OF_RULES_TO_LINK": "Liste med regler, som forbindes",
"ERRORS": {
"REQUEST_FAILED": "Fejl, mens du prøvede at oprette en forbindelse til et regelsæt"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Fejl, mens du prøvede at slette en forbindelse fra et regelsæt"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Regeln verwalten"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Abbrechen",
"CREATE": "Erstellen",
"CREATE_TITLE": "Eine Regel erstellen",
"UPDATE": "Aktualisieren",
"UPDATE_TITLE": "Eine Regel bearbeiten"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Name",
"DESCRIPTION": "Beschreibung",
"WHEN": "Wenn",
"PERFORM_ACTIONS": "Aktionen durchführen",
"OPTIONS": "Andere Optionen"
},
"PLACEHOLDER": {
"NAME": "Regelname eingeben",
"DESCRIPTION": "Regelbeschreibung eingeben",
"NO_DESCRIPTION": "Keine Beschreibung",
"VALUE": "Wert",
"CHOOSE_FOLDER": "Zielordner auswählen"
},
"ERROR": {
"REQUIRED": "Dieses Feld ist erforderlich",
"RULE_COMPOSITE_CONDITION_INVALID": "Eine oder mehrere Konditionsgruppen sind leer",
"INSUFFICIENT_TRIGGERS_SELECTED": "Mindestens ein Auslöser ist erforderlich"
},
"TRIGGERS": {
"INBOUND": "Elemente werden erstellt oder in diesen Ordner eingegeben",
"UPDATE": "Elemente werden aktualisiert",
"OUTBOUND": "Elemente werden gelöscht oder verlassen diesen Ordner"
},
"OPTIONS": {
"IS_INHERITABLE": "Regel gilt für Unterordner",
"IS_ASYNCHRONOUS": "Regel im Hintergrund ausführen",
"DISABLE_RULE": "Regel deaktivieren",
"ERROR_SCRIPT": "Wenn Fehler auftreten, Skript ausführen",
"NO_SCRIPT": "Keine"
},
"COMPARATORS": {
"EQUALS": "(=) Gleich",
"CONTAINS": "Enthält",
"STARTS_WITH": "Beginnt mit",
"ENDS_WITH": "Endet mit",
"GREATER_THAN": "(>) Größer als",
"LESS_THAN": "(<) Kleiner als",
"GREATER_THAN_OR_EQUAL": "(>=) Größer als oder gleich",
"LESS_THAN_OR_EQUAL": "(<=) Kleiner als oder gleich",
"ON": "(=) Am",
"AFTER": "(>) Nach",
"BEFORE": "(<) Vor",
"ON_OR_AFTER": "(>=) Am oder nach",
"ON_OR_BEFORE": "(<=) Am oder vor",
"INSTANCE_OF": "(=) Ist"
},
"FIELDS": {
"NAME": "Name",
"SIZE": "Größe",
"MIMETYPE": "MIME-Type",
"ENCODING": "Kodierung",
"HAS_CATEGORY": "Hat Kategorie",
"HAS_TAG": "Hat Tag",
"HAS_ASPECT": "Hat Aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Wenn",
"NOT_IF": "NICHT Wenn",
"AND": "Und",
"OR": "Oder"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Kondition hinzufügen",
"ADD_GROUP": "Konditionsgruppe hinzufügen",
"REMOVE": "Entfernen"
},
"NO_CONDITIONS": "Keine Kondition",
"NO_CONDITIONS_IN_GROUP": "Keine Kondition in der Gruppe",
"ACTION_BUTTONS": {
"ADD_ACTION": "Aktion hinzufügen",
"REMOVE": "Entfernen"
},
"ACTION_SELECT_PLACEHOLDER": "Eine Aktion auswählen"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "Regeln"
},
"ACTIONS": {
"CREATE_RULE": "Eine Regel erstellen",
"LINK_RULES": "Link-Regeln",
"EDIT_RULE": "Bearbeiten",
"SEE_IN_FOLDER": "Siehe im Ordner",
"INHERIT_RULES": "Regeln vererben"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Diese Liste ist leer",
"SUBTITLE": "Es sind noch keine Regeln für diesen Ordner definiert."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Regel löschen",
"MESSAGE": "Möchten Sie diese Regel wirklich löschen?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Link für Regelsatz löschen",
"MESSAGE": "Sind Sie sicher, dass Sie den Link zu diesem Regelsatz löschen möchten?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regeln aus aktuellem Ordner",
"LINKED_RULES": "Regeln aus verknüpften Ordnern",
"INHERITED_RULES": "Vererbte Regeln",
"LOAD_MORE_RULE_SETS": "Regeln aus anderen Ordnern laden",
"LOAD_MORE_RULES": "Weitere Regeln laden",
"LOADING_RULES": "Regeln werden geladen",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Vererbte Regeln werden zuerst ausgeführt",
"ALL_LINKED_RULES_ARE_DISABLED": "Alle von diesem Regelsatz verknüpften Regeln sind deaktiviert"
},
"LINK_RULES_DIALOG": {
"TITLE": "Wählen Sie einen Ordner für die Verknüpfung von Regeln aus",
"CANCEL": "Abbrechen",
"SUBMIT": "Ordner auswählen",
"EMPTY_RULES_LIST": {
"TITLE": "Keine eigenen Regeln",
"SUBTITLE": "Der ausgewählte Ordner hat keine eigenen Regeln."
},
"LIST_OF_RULES_TO_LINK": "Liste der Regeln, die verknüpft werden sollen",
"ERRORS": {
"REQUEST_FAILED": "Fehler beim Versuch, einen Link zu einem Regelsatz zu erstellen, ist aufgetreten"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Fehler beim Versuch, einen Link aus einem Regelsatz zu löschen, ist aufgetreten"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Manage Rules"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Cancel",
"CREATE": "Create",
"CREATE_TITLE": "Create a rule",
"UPDATE": "Update",
"UPDATE_TITLE": "Edit a rule"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Name",
"DESCRIPTION": "Description",
"WHEN": "When",
"PERFORM_ACTIONS": "Perform actions",
"OPTIONS": "Other options"
},
"PLACEHOLDER": {
"NAME": "Enter rule name",
"DESCRIPTION": "Enter rule description",
"NO_DESCRIPTION": "No description",
"VALUE": "Value",
"CHOOSE_FOLDER": "Choose destination folder"
},
"ERROR": {
"REQUIRED": "This field is required",
"RULE_COMPOSITE_CONDITION_INVALID": "One or more condition groups is empty",
"INSUFFICIENT_TRIGGERS_SELECTED": "At least one trigger is required"
},
"TRIGGERS": {
"INBOUND": "Items are created or enter this folder",
"UPDATE": "Items are updated",
"OUTBOUND": "Items are deleted or leave this folder"
},
"OPTIONS": {
"IS_INHERITABLE": "Rule applies to subfolders",
"IS_ASYNCHRONOUS": "Run rule in the background",
"DISABLE_RULE": "Disable rule",
"ERROR_SCRIPT": "If errors occur run script",
"NO_SCRIPT": "None"
},
"COMPARATORS": {
"EQUALS": "(=) Equals",
"CONTAINS": "Contains",
"STARTS_WITH": "Starts with",
"ENDS_WITH": "Ends with",
"GREATER_THAN": "(>) Greater than",
"LESS_THAN": "(<) Less than",
"GREATER_THAN_OR_EQUAL": "(>=) Greater than or equal",
"LESS_THAN_OR_EQUAL": "(<=) Less than or equal",
"ON": "(=) On",
"AFTER": "(>) After",
"BEFORE": "(<) Before",
"ON_OR_AFTER": "(>=) On or after",
"ON_OR_BEFORE": "(<=) On or before",
"INSTANCE_OF": "(=) Is"
},
"FIELDS": {
"NAME": "Name",
"SIZE": "Size",
"MIMETYPE": "Mimetype",
"ENCODING": "Encoding",
"HAS_CATEGORY": "Has category",
"HAS_TAG": "Has tag",
"HAS_ASPECT": "Has aspect"
},
"LOGIC_OPERATORS": {
"IF": "If",
"NOT_IF": "NOT If",
"AND": "And",
"OR": "Or"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Add condition",
"ADD_GROUP": "Add condition group",
"REMOVE": "Remove"
},
"NO_CONDITIONS": "No conditions",
"NO_CONDITIONS_IN_GROUP": "No conditions in the group",
"ACTION_BUTTONS": {
"ADD_ACTION": "Add action",
"REMOVE": "Remove"
},
"ACTION_SELECT_PLACEHOLDER": "Select an action"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "rules"
},
"ACTIONS": {
"CREATE_RULE": "Create rule",
"LINK_RULES": "Link rules",
"EDIT_RULE": "Edit",
"SEE_IN_FOLDER": "See in folder",
"INHERIT_RULES": "Inherit rules"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "The list is empty",
"SUBTITLE": "There are no rules defined for this folder yet."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Delete rule",
"MESSAGE": "Are you sure you want to delete this rule?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Delete rule set link",
"MESSAGE": "Are you sure you want to delete the link to this rule set?"
}
},
"RULE_LIST": {
"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",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Inherited rules will be run first",
"ALL_LINKED_RULES_ARE_DISABLED": "All rules linked from this rule set are disabled"
},
"LINK_RULES_DIALOG": {
"TITLE": "Select a folder to link rules from",
"CANCEL": "Cancel",
"SUBMIT": "Select folder",
"EMPTY_RULES_LIST": {
"TITLE": "No owned rules",
"SUBTITLE": "The selected folder doesn't have any rules of its own."
},
"LIST_OF_RULES_TO_LINK": "List of rules that will be linked",
"ERRORS": {
"REQUEST_FAILED": "Error while trying to create a link to a rule set"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Error while trying to delete a link from a rule set"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Gestionar reglas"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Cancelar",
"CREATE": "Crear",
"CREATE_TITLE": "Crear una regla",
"UPDATE": "Actualizar",
"UPDATE_TITLE": "Editar la regla"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nombre",
"DESCRIPTION": "Descripción",
"WHEN": "Cuando",
"PERFORM_ACTIONS": "Realizar acciones",
"OPTIONS": "Otras opciones"
},
"PLACEHOLDER": {
"NAME": "Introducir un nombre para la regla",
"DESCRIPTION": "Introducir una descripción para la regla",
"NO_DESCRIPTION": "Sin descripción",
"VALUE": "Valor",
"CHOOSE_FOLDER": "Seleccione la carpeta de destino"
},
"ERROR": {
"REQUIRED": "Este campo es obligatorio",
"RULE_COMPOSITE_CONDITION_INVALID": "Uno o más grupos de condiciones están vacíos",
"INSUFFICIENT_TRIGGERS_SELECTED": "Se requiere al menos un activador"
},
"TRIGGERS": {
"INBOUND": "Los elementos se crean o ingresan a esta carpeta",
"UPDATE": "Los elementos se actualizan",
"OUTBOUND": "Los elementos se eliminan o abandonan esta carpeta"
},
"OPTIONS": {
"IS_INHERITABLE": "La regla se aplica a las subcarpetas",
"IS_ASYNCHRONOUS": "Ejecutar la regla en segundo plano",
"DISABLE_RULE": "Desactivar regla",
"ERROR_SCRIPT": "Si se producen errores, ejecute el script",
"NO_SCRIPT": "Ninguna"
},
"COMPARATORS": {
"EQUALS": "(=) Igual a",
"CONTAINS": "Contiene",
"STARTS_WITH": "Comienza con",
"ENDS_WITH": "Termina con",
"GREATER_THAN": "(>) Mayor que",
"LESS_THAN": "(<) Menor que",
"GREATER_THAN_OR_EQUAL": "(>=) Mayor que o igual",
"LESS_THAN_OR_EQUAL": "(<=) Menor que o igual",
"ON": "(=) En",
"AFTER": "(>) Después",
"BEFORE": "(<) Antes",
"ON_OR_AFTER": "(>=) En o después",
"ON_OR_BEFORE": "(<=) En o antes",
"INSTANCE_OF": "(=) Es"
},
"FIELDS": {
"NAME": "Nombre",
"SIZE": "Tamaño",
"MIMETYPE": "Tipo MIME",
"ENCODING": "Codificación",
"HAS_CATEGORY": "Tiene categoría",
"HAS_TAG": "Tiene etiqueta",
"HAS_ASPECT": "Tiene aspecto"
},
"LOGIC_OPERATORS": {
"IF": "Si",
"NOT_IF": "NO si es que",
"AND": "Y",
"OR": "O"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Añadir condición",
"ADD_GROUP": "Añadir grupo de condiciones",
"REMOVE": "Eliminar"
},
"NO_CONDITIONS": "Sin condiciones",
"NO_CONDITIONS_IN_GROUP": "Sin condiciones en el grupo",
"ACTION_BUTTONS": {
"ADD_ACTION": "Añadir acción",
"REMOVE": "Eliminar"
},
"ACTION_SELECT_PLACEHOLDER": "Seleccionar una acción"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "reglas"
},
"ACTIONS": {
"CREATE_RULE": "Crear una regla",
"LINK_RULES": "Reglas de enlace",
"EDIT_RULE": "Editar",
"SEE_IN_FOLDER": "Ver en carpeta",
"INHERIT_RULES": "Heredar reglas"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Esta lista está vacía",
"SUBTITLE": "Todavía no hay reglas definidas para esta carpeta."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Eliminar regla",
"MESSAGE": "¿Está seguro de que desea eliminar esta regla?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Eliminar el enlace del conjunto de reglas",
"MESSAGE": "¿Está seguro de que quiere eliminar el enlace a este conjunto de reglas?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Reglas de la carpeta actual",
"LINKED_RULES": "Reglas de la carpeta vinculada",
"INHERITED_RULES": "Reglas heredadas",
"LOAD_MORE_RULE_SETS": "Cargar reglas de otras carpetas",
"LOAD_MORE_RULES": "Cargar más reglas",
"LOADING_RULES": "Cargando reglas",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Las reglas heredadas se ejecutarán primero",
"ALL_LINKED_RULES_ARE_DISABLED": "Todas las reglas vinculadas a este conjunto de reglas están deshabilitadas"
},
"LINK_RULES_DIALOG": {
"TITLE": "Seleccione una carpeta desde la que vincular las reglas",
"CANCEL": "Cancelar",
"SUBMIT": "Seleccionar carpeta",
"EMPTY_RULES_LIST": {
"TITLE": "No hay reglas de propiedad",
"SUBTITLE": "La carpeta seleccionada no tiene reglas propias."
},
"LIST_OF_RULES_TO_LINK": "Lista de reglas que se vincularán",
"ERRORS": {
"REQUEST_FAILED": "Se produjo un error al intentar crear un enlace a un conjunto de reglas"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Se produjo un error al intentar eliminar un enlace de un conjunto de reglas"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Hallitse sääntöjä"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Peruuta",
"CREATE": "Luo",
"CREATE_TITLE": "Luo sääntö",
"UPDATE": "Päivitä",
"UPDATE_TITLE": "Muokkaa sääntöä"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nimi",
"DESCRIPTION": "Kuvaus",
"WHEN": "Kun",
"PERFORM_ACTIONS": "Suorita toimintoja",
"OPTIONS": "Muut valinnat"
},
"PLACEHOLDER": {
"NAME": "Syötä säännön nimi",
"DESCRIPTION": "Syötä säännön kuvaus",
"NO_DESCRIPTION": "Ei kuvausta",
"VALUE": "Arvo",
"CHOOSE_FOLDER": "Valitse kohdekansio"
},
"ERROR": {
"REQUIRED": "Tämä kenttä on pakollinen",
"RULE_COMPOSITE_CONDITION_INVALID": "Yksi tai useampi ehtoryhmä on tyhjä",
"INSUFFICIENT_TRIGGERS_SELECTED": "Vähintään yksi laukaisija on pakollinen"
},
"TRIGGERS": {
"INBOUND": "Kohteet luodaan tai syötetään tähän kansioon",
"UPDATE": "Kohteet päivitetään",
"OUTBOUND": "Kohteet poistetaan tai ne poistuvat tästä kansiosta"
},
"OPTIONS": {
"IS_INHERITABLE": "Sääntö koskee alikansioita",
"IS_ASYNCHRONOUS": "Suorita sääntö taustalla",
"DISABLE_RULE": "Poista sääntö käytöstä",
"ERROR_SCRIPT": "Suorita komentosarja, jos virheitä havaitaan",
"NO_SCRIPT": "Ei mitään"
},
"COMPARATORS": {
"EQUALS": "(=) Yhtä suuri kuin",
"CONTAINS": "Sisältää",
"STARTS_WITH": "Alkaa merkillä",
"ENDS_WITH": "Loppuu merkkiin",
"GREATER_THAN": "(>) Suurempi kuin",
"LESS_THAN": "(<) Pienempi kuin",
"GREATER_THAN_OR_EQUAL": "(>=) Suurempi tai yhtä suuri kuin",
"LESS_THAN_OR_EQUAL": "(<=) Vähemmän tai yhtä paljon",
"ON": "(=) Tänä ajankohtana",
"AFTER": "(>) Jälkeen",
"BEFORE": "(<) Ennen",
"ON_OR_AFTER": "(>=) Tänä ajankohtana tai sen jälkeen",
"ON_OR_BEFORE": "(<=) Tänä ajankohtana tai sitä ennen",
"INSTANCE_OF": "(=) On"
},
"FIELDS": {
"NAME": "Nimi",
"SIZE": "Koko",
"MIMETYPE": "MIME-tyyppi",
"ENCODING": "Koodaus",
"HAS_CATEGORY": "Sisältää luokan",
"HAS_TAG": "Sisältää tunnuksen",
"HAS_ASPECT": "Sisältää ominaisuusjoukon"
},
"LOGIC_OPERATORS": {
"IF": "Jos",
"NOT_IF": "EI jos",
"AND": "Ja",
"OR": "Tai"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Lisää ehto",
"ADD_GROUP": "Lisää ehtoryhmä",
"REMOVE": "Poista"
},
"NO_CONDITIONS": "Ei ehtoja",
"NO_CONDITIONS_IN_GROUP": "Ryhmässä ei ole ehtoja",
"ACTION_BUTTONS": {
"ADD_ACTION": "Lisää toiminto",
"REMOVE": "Poista"
},
"ACTION_SELECT_PLACEHOLDER": "Valitse toiminto"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "säännöt"
},
"ACTIONS": {
"CREATE_RULE": "Luo sääntö",
"LINK_RULES": "Linkitä sääntöjä",
"EDIT_RULE": "Muokkaa",
"SEE_IN_FOLDER": "Katso kansiosta",
"INHERIT_RULES": "Peri sääntöjä"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Luettelo on tyhjä",
"SUBTITLE": "Tälle kansiolle ei ole vielä määritelty sääntöjä."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Poista sääntö",
"MESSAGE": "Haluatko varmasti poistaa tämän säännön?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Poista sääntöjoukon linkki",
"MESSAGE": "Haluatko varmasti poistaa tähän sääntöjoukkoon johtavan linkin?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Säännöt nykyisestä kansiosta",
"LINKED_RULES": "Säännöt linkitetystä kansiosta",
"INHERITED_RULES": "Perityt säännöt",
"LOAD_MORE_RULE_SETS": "Lataa sääntöjä muista kansioista",
"LOAD_MORE_RULES": "Lataa lisää sääntöjä",
"LOADING_RULES": "Sääntöjä ladataan",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Perityt säännöt suoritetaan ensin",
"ALL_LINKED_RULES_ARE_DISABLED": "Kaikki tästä sääntöjoukosta linkitetyt säännöt on poistettu käytöstä"
},
"LINK_RULES_DIALOG": {
"TITLE": "Valitse kansio, josta sääntöjä linkitetään",
"CANCEL": "Peruuta",
"SUBMIT": "Valitse kansio",
"EMPTY_RULES_LIST": {
"TITLE": "Ei omistettuja sääntöjä",
"SUBTITLE": "Valitulla kansiolla ei ole omia sääntöjä."
},
"LIST_OF_RULES_TO_LINK": "Luettelo säännöistä, jotka linkitetään",
"ERRORS": {
"REQUEST_FAILED": "Virhe yritettäessä luoda linkki sääntöjoukkoon"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Virhe yritettäessä poistaa linkki sääntöjoukosta"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Gérer les règles"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Annuler",
"CREATE": "Créer",
"CREATE_TITLE": "Créer une règle",
"UPDATE": "Mettre à jour",
"UPDATE_TITLE": "Modifier une règle"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nom",
"DESCRIPTION": "Description",
"WHEN": "Quand",
"PERFORM_ACTIONS": "Effectuer des actions",
"OPTIONS": "Autres options"
},
"PLACEHOLDER": {
"NAME": "Entrer le nom de la règle",
"DESCRIPTION": "Entrer la description de la règle",
"NO_DESCRIPTION": "Aucune description",
"VALUE": "Valeur",
"CHOOSE_FOLDER": "Choisir le dossier de destination"
},
"ERROR": {
"REQUIRED": "Ce champ est obligatoire",
"RULE_COMPOSITE_CONDITION_INVALID": "Un ou plusieurs groupes de conditions sont vides",
"INSUFFICIENT_TRIGGERS_SELECTED": "Au moins un déclencheur est requis"
},
"TRIGGERS": {
"INBOUND": "Des éléments sont créés ou ajoutés à ce dossier",
"UPDATE": "Des éléments sont mis à jour",
"OUTBOUND": "Des éléments sont supprimés ou retirés de ce dossier"
},
"OPTIONS": {
"IS_INHERITABLE": "La règle s'applique aux sous-dossiers",
"IS_ASYNCHRONOUS": "Exécuter la règle en arrière-plan",
"DISABLE_RULE": "Désactiver la règle",
"ERROR_SCRIPT": "Si des erreurs surviennent, exécuter le script",
"NO_SCRIPT": "Aucun"
},
"COMPARATORS": {
"EQUALS": "(=) Est égal à",
"CONTAINS": "Contient",
"STARTS_WITH": "Commence par",
"ENDS_WITH": "Finit par",
"GREATER_THAN": "(>) Supérieur à",
"LESS_THAN": "(<) Inférieur à",
"GREATER_THAN_OR_EQUAL": "(>=) Supérieur ou égal à",
"LESS_THAN_OR_EQUAL": "(<=) Inférieur ou égal à",
"ON": "(=) A cet instant",
"AFTER": "(>) Après",
"BEFORE": "(<) Avant",
"ON_OR_AFTER": "(>=) A cet instant ou après",
"ON_OR_BEFORE": "(<=) A cet instant ou avant",
"INSTANCE_OF": "(=) Est"
},
"FIELDS": {
"NAME": "Nom",
"SIZE": "Taille",
"MIMETYPE": "Type MIME",
"ENCODING": "Encodage",
"HAS_CATEGORY": "A une catégorie",
"HAS_TAG": "A un tag",
"HAS_ASPECT": "A un aspect"
},
"LOGIC_OPERATORS": {
"IF": "Si",
"NOT_IF": "PAS Si",
"AND": "Et",
"OR": "Ou"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Ajouter une condition",
"ADD_GROUP": "Ajouter un groupe de conditions",
"REMOVE": "Supprimer"
},
"NO_CONDITIONS": "Aucune condition",
"NO_CONDITIONS_IN_GROUP": "Aucune condition dans le groupe",
"ACTION_BUTTONS": {
"ADD_ACTION": "Ajouter une action",
"REMOVE": "Supprimer"
},
"ACTION_SELECT_PLACEHOLDER": "Sélectionner une action"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "règles"
},
"ACTIONS": {
"CREATE_RULE": "Créer une règle",
"LINK_RULES": "Lier des règles",
"EDIT_RULE": "Modifier",
"SEE_IN_FOLDER": "Voir dans le dossier",
"INHERIT_RULES": "Hériter des règles"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "La liste est vide",
"SUBTITLE": "Aucune règle définie pour ce dossier pour l'instant."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Supprimez la règle",
"MESSAGE": "Voulez-vous vraiment supprimer cette règle ?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Supprimer le lien vers l'ensemble de règles",
"MESSAGE": "Voulez-vous vraiment supprimer le lien vers cet ensemble de règles ?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Règles du dossier actuel",
"LINKED_RULES": "Règles du dossier lié",
"INHERITED_RULES": "Règles héritées",
"LOAD_MORE_RULE_SETS": "Charger les règles d'autres dossiers",
"LOAD_MORE_RULES": "Charger plus de règles",
"LOADING_RULES": "Chargement des règles en cours",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Les règles héritées seront exécutées en premier",
"ALL_LINKED_RULES_ARE_DISABLED": "Toutes les règles liées à cet ensemble de règles sont désactivées"
},
"LINK_RULES_DIALOG": {
"TITLE": "Sélectionner le dossier à partir duquel lier les règles",
"CANCEL": "Annuler",
"SUBMIT": "Sélectionner le dossier",
"EMPTY_RULES_LIST": {
"TITLE": "Aucune règle acquise",
"SUBTITLE": "Le dossier sélectionné n'a pas ses propres règles."
},
"LIST_OF_RULES_TO_LINK": "Liste des règles qui seront liées",
"ERRORS": {
"REQUEST_FAILED": "Erreur lors de la tentative de création d'un lien vers un ensemble de règles"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Erreur lors de la tentative de suppression d'un lien avec un ensemble de règles"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Gestisci regole"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Annulla",
"CREATE": "Crea",
"CREATE_TITLE": "Crea una regola",
"UPDATE": "Aggiorna",
"UPDATE_TITLE": "Modifica una regola"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nome",
"DESCRIPTION": "Descrizione",
"WHEN": "Quando",
"PERFORM_ACTIONS": "Esegui azioni",
"OPTIONS": "Altre opzioni"
},
"PLACEHOLDER": {
"NAME": "Immetti nome regola",
"DESCRIPTION": "Immetti descrizione regola",
"NO_DESCRIPTION": "Nessuna descrizione",
"VALUE": "Valore",
"CHOOSE_FOLDER": "Scegli cartella di destinazione"
},
"ERROR": {
"REQUIRED": "Questo campo è obbligatorio",
"RULE_COMPOSITE_CONDITION_INVALID": "Uno o più gruppi di condizioni sono vuoti.",
"INSUFFICIENT_TRIGGERS_SELECTED": "È necessario almeno un trigger"
},
"TRIGGERS": {
"INBOUND": "Vengono creati gli elementi o si accede alla cartella",
"UPDATE": "Vengono aggiornati gli elementi",
"OUTBOUND": "Vengono eliminati gli elementi o si esce dalla cartella"
},
"OPTIONS": {
"IS_INHERITABLE": "Regola applicabile alle sottocartelle",
"IS_ASYNCHRONOUS": "Esegui regola in background",
"DISABLE_RULE": "Disabilita regola",
"ERROR_SCRIPT": "Esegui script in caso di errori",
"NO_SCRIPT": "Nessuna"
},
"COMPARATORS": {
"EQUALS": "(=) Uguale a",
"CONTAINS": "Contiene",
"STARTS_WITH": "Inizia con",
"ENDS_WITH": "Finisce con",
"GREATER_THAN": "(>) Maggiore di",
"LESS_THAN": "(<) Minore di",
"GREATER_THAN_OR_EQUAL": "(>=) Maggiore di o uguale a",
"LESS_THAN_OR_EQUAL": "(<=) Minore di o uguale a",
"ON": "(=) Il",
"AFTER": "(>) Dopo",
"BEFORE": "(<) Prima",
"ON_OR_AFTER": "(>=) Il o dopo",
"ON_OR_BEFORE": "(<=) Il o prima",
"INSTANCE_OF": "(=) È"
},
"FIELDS": {
"NAME": "Nome",
"SIZE": "Dimensione",
"MIMETYPE": "Tipo MIME",
"ENCODING": "Codifica",
"HAS_CATEGORY": "Con categoria",
"HAS_TAG": "Con tag",
"HAS_ASPECT": "Con aspetto"
},
"LOGIC_OPERATORS": {
"IF": "If",
"NOT_IF": "NOT If",
"AND": "And",
"OR": "Or"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Aggiungi condizione",
"ADD_GROUP": "Aggiungi gruppo di condizioni",
"REMOVE": "Rimuovi"
},
"NO_CONDITIONS": "Nessuna condizione",
"NO_CONDITIONS_IN_GROUP": "Nessuna condizione nel gruppo",
"ACTION_BUTTONS": {
"ADD_ACTION": "Aggiungi azione",
"REMOVE": "Rimuovi"
},
"ACTION_SELECT_PLACEHOLDER": "Seleziona un'azione"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regole"
},
"ACTIONS": {
"CREATE_RULE": "Crea una regola",
"LINK_RULES": "Collega le regole",
"EDIT_RULE": "Modifica",
"SEE_IN_FOLDER": "Visualizza nella cartella",
"INHERIT_RULES": "Eredita le regole"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "L'elenco è vuoto",
"SUBTITLE": "Non sono ancora state definite delle regole per questa cartella."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Elimina regola",
"MESSAGE": "Eliminare questa regola?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Elimina collegamento al set di regole",
"MESSAGE": "Eliminare il collegamento a questo set di regole?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regole dalla cartella corrente",
"LINKED_RULES": "Regole dalla cartella collegata",
"INHERITED_RULES": "Regole ereditate",
"LOAD_MORE_RULE_SETS": "Carica regole da altre cartelle",
"LOAD_MORE_RULES": "Carica altre regole",
"LOADING_RULES": "Caricamento delle regole in corso",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Le regole ereditate verranno eseguite per prime",
"ALL_LINKED_RULES_ARE_DISABLED": "Tutte le regole collegate a questo set di regole verranno disabilitate"
},
"LINK_RULES_DIALOG": {
"TITLE": "Seleziona una cartella da cui collegare le regole",
"CANCEL": "Annulla",
"SUBMIT": "Seleziona cartella",
"EMPTY_RULES_LIST": {
"TITLE": "Nessuna regola di proprietà",
"SUBTITLE": "La cartella selezionata non ha regole proprie."
},
"LIST_OF_RULES_TO_LINK": "Elenco delle regole che verranno collegate",
"ERRORS": {
"REQUEST_FAILED": "Errore durante il tentativo di creare un collegamento a un set di regole"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Errore durante il tentativo di eliminare un collegamento da un set di regole"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "ルールの管理"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "キャンセル",
"CREATE": "作成",
"CREATE_TITLE": "ルールの作成",
"UPDATE": "更新",
"UPDATE_TITLE": "ルールの編集"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "名前",
"DESCRIPTION": "説明",
"WHEN": "日時",
"PERFORM_ACTIONS": "アクションの実行",
"OPTIONS": "他のオプション"
},
"PLACEHOLDER": {
"NAME": "ルールの名前を入力します",
"DESCRIPTION": "ルールの説明を入力します",
"NO_DESCRIPTION": "説明なし",
"VALUE": "値",
"CHOOSE_FOLDER": "移動先フォルダの選択"
},
"ERROR": {
"REQUIRED": "このフィールドは必須です",
"RULE_COMPOSITE_CONDITION_INVALID": "1つ以上の条件グループが空です",
"INSUFFICIENT_TRIGGERS_SELECTED": "少なくとも1つのトリガーが必要です"
},
"TRIGGERS": {
"INBOUND": "アイタムがこのフォルダに作成または入力される",
"UPDATE": "アイタムが更新される",
"OUTBOUND": "アイタムがこのフォルダから削除または送り出される"
},
"OPTIONS": {
"IS_INHERITABLE": "ルールはサブフォルダに適用",
"IS_ASYNCHRONOUS": "ルールを背景で実行",
"DISABLE_RULE": "ルールの無効化",
"ERROR_SCRIPT": "エラーが発生した場合、スクリプトを実行",
"NO_SCRIPT": "なし"
},
"COMPARATORS": {
"EQUALS": "(=) 等しい",
"CONTAINS": "含む",
"STARTS_WITH": "で始まる",
"ENDS_WITH": "で終わる",
"GREATER_THAN": "(>) より大きい",
"LESS_THAN": "(<) より小さい",
"GREATER_THAN_OR_EQUAL": "(>=) より大きいか等しい",
"LESS_THAN_OR_EQUAL": "(<=) より小さいか等しい",
"ON": "(=) その日",
"AFTER": "(>) その日の後",
"BEFORE": "(<) その日の前",
"ON_OR_AFTER": "(>=) その日か後",
"ON_OR_BEFORE": "(<=) その日か前",
"INSTANCE_OF": "(=) 等しい"
},
"FIELDS": {
"NAME": "名前",
"SIZE": "サイズ",
"MIMETYPE": "MIME タイプ",
"ENCODING": "エンコーディング",
"HAS_CATEGORY": "カテゴリがない",
"HAS_TAG": "タグがある",
"HAS_ASPECT": "アスペクトがある"
},
"LOGIC_OPERATORS": {
"IF": "次の場合",
"NOT_IF": "次でない場合",
"AND": "および",
"OR": "または"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "条件の追加",
"ADD_GROUP": "条件グループの追加",
"REMOVE": "削除"
},
"NO_CONDITIONS": "条件なし",
"NO_CONDITIONS_IN_GROUP": "グループに条件なし",
"ACTION_BUTTONS": {
"ADD_ACTION": "処理の追加",
"REMOVE": "削除"
},
"ACTION_SELECT_PLACEHOLDER": "アクションの選択"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "ルール"
},
"ACTIONS": {
"CREATE_RULE": "ルールの作成",
"LINK_RULES": "リンクルール",
"EDIT_RULE": "編集",
"SEE_IN_FOLDER": "フォルダで見る",
"INHERIT_RULES": "ルールの継承"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "このリストは空です",
"SUBTITLE": "このフォルダーにはまだルールが定義されていません。"
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "ルールの削除",
"MESSAGE": "このルールを削除しますか?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "ルールセットリンクの削除",
"MESSAGE": "このルールセットへのリンクを削除しますか?"
}
},
"RULE_LIST": {
"OWNED_RULES": "現在のフォルダのルール",
"LINKED_RULES": "リンク先フォルダのルール",
"INHERITED_RULES": "継承されたルール",
"LOAD_MORE_RULE_SETS": "他のフォルダからユールを読み込む",
"LOAD_MORE_RULES": "さらにルールを読み込む",
"LOADING_RULES": "ルールの読み込み中",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "継承されたルールが最初に実行されます",
"ALL_LINKED_RULES_ARE_DISABLED": "このルールセットからリンクされているすべてのルールは無効です"
},
"LINK_RULES_DIALOG": {
"TITLE": "ルールをリンクさせるフォルダを選択",
"CANCEL": "キャンセル",
"SUBMIT": "フォルダの選択",
"EMPTY_RULES_LIST": {
"TITLE": "所有ルールがありません",
"SUBTITLE": "選択したフォルダには、それ自身のルールがありません。"
},
"LIST_OF_RULES_TO_LINK": "リンク先のルールリスト",
"ERRORS": {
"REQUEST_FAILED": "ルールセットへのリンクを作成中にエラーが発生しました"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "ルールセットからリンクを削除中にエラーが発生しました"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Administrer regler"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Avbryt",
"CREATE": "Opprett",
"CREATE_TITLE": "Opprett en regel",
"UPDATE": "Oppdater",
"UPDATE_TITLE": "Rediger en regel"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Navn",
"DESCRIPTION": "Beskrivelse",
"WHEN": "Når",
"PERFORM_ACTIONS": "Utfør handlinger",
"OPTIONS": "Andre alternativer"
},
"PLACEHOLDER": {
"NAME": "Tast inn et navn på regelen",
"DESCRIPTION": "Tast inn en beskrivelse av regelen",
"NO_DESCRIPTION": "Ingen beskrivelse",
"VALUE": "Verdi",
"CHOOSE_FOLDER": "Velg målmappe"
},
"ERROR": {
"REQUIRED": "Dette feltet er påkrevd",
"RULE_COMPOSITE_CONDITION_INVALID": "En eller flere av betingelsesgruppene er tomme",
"INSUFFICIENT_TRIGGERS_SELECTED": "Er det obligatorisk med minst en utløser"
},
"TRIGGERS": {
"INBOUND": "Enheter blir opprettet etter lagt til i denne mappen",
"UPDATE": "Enhetene er oppdaterte",
"OUTBOUND": "Enhetene er slettet eller har forlatt denne mappen"
},
"OPTIONS": {
"IS_INHERITABLE": "Regel brukt på undermapper",
"IS_ASYNCHRONOUS": "Kjør regel i bakgrunnen",
"DISABLE_RULE": "Deaktiver regel",
"ERROR_SCRIPT": "Kjør skript hvis feil oppstår",
"NO_SCRIPT": "Ingen"
},
"COMPARATORS": {
"EQUALS": "(=) Er lik",
"CONTAINS": "Inneholder",
"STARTS_WITH": "Starter med",
"ENDS_WITH": "Slutter med",
"GREATER_THAN": "(>) Større enn",
"LESS_THAN": "(>) Mindre enn",
"GREATER_THAN_OR_EQUAL": "(>=) Større enn eller lik",
"LESS_THAN_OR_EQUAL": "(<=) Mindre enn eller lik",
"ON": "(=) På",
"AFTER": "(>) Etter",
"BEFORE": "(<) Før",
"ON_OR_AFTER": "(>=) På eller etter",
"ON_OR_BEFORE": "(<=) På eller før",
"INSTANCE_OF": "(=) Er"
},
"FIELDS": {
"NAME": "Navn",
"SIZE": "Størrelse",
"MIMETYPE": "Mimetype",
"ENCODING": "Koding",
"HAS_CATEGORY": "Ingen kategori",
"HAS_TAG": "Har tag",
"HAS_ASPECT": "Har aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Hvis",
"NOT_IF": "IKKE hvis",
"AND": "Og",
"OR": "Eller"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Legg til betingelse",
"ADD_GROUP": "Legg til betingelsesgruppe",
"REMOVE": "Fjern"
},
"NO_CONDITIONS": "Ingen betingelser",
"NO_CONDITIONS_IN_GROUP": "Ingen betingelse i gruppen",
"ACTION_BUTTONS": {
"ADD_ACTION": "Legg til handling",
"REMOVE": "Fjern"
},
"ACTION_SELECT_PLACEHOLDER": "Velg en handling"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regler"
},
"ACTIONS": {
"CREATE_RULE": "Opprett en regel",
"LINK_RULES": "Koble regler",
"EDIT_RULE": "Rediger",
"SEE_IN_FOLDER": "Se i mappe",
"INHERIT_RULES": "Arve regler"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Denne listen er tom",
"SUBTITLE": "Det er ingen regler definert for denne mappen enda."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Slett regel",
"MESSAGE": "Er du sikker på at du vil slette denne regelen?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Slett regelsettkobling",
"MESSAGE": "Er du sikker på at du vil slette koblingen til dette regelsettet?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regler fra gjeldende mappe",
"LINKED_RULES": "Regler fra koblet mappe",
"INHERITED_RULES": "Arvede regler",
"LOAD_MORE_RULE_SETS": "Last regler fra andre mapper",
"LOAD_MORE_RULES": "Last flere regler",
"LOADING_RULES": "Laster regler",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Arvede regler kjøres først",
"ALL_LINKED_RULES_ARE_DISABLED": "Alle regler som er stammer fra dette regelsettet, er deaktiverte"
},
"LINK_RULES_DIALOG": {
"TITLE": "Velg en mappe å koble regler fra",
"CANCEL": "Avbryt",
"SUBMIT": "Velg mappe",
"EMPTY_RULES_LIST": {
"TITLE": "Ingen eide regler",
"SUBTITLE": "Den valgte mappen har ingen egne regler."
},
"LIST_OF_RULES_TO_LINK": "Liste over regler som vil bli koblet",
"ERRORS": {
"REQUEST_FAILED": "Feil ved forsøk på å opprette en kobling til et regelsett"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Feil ved forsøk på å slette en kobling fra et regelsett"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Regels beheren"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Annuleren",
"CREATE": "Maken",
"CREATE_TITLE": "Een regel maken",
"UPDATE": "Bijwerken",
"UPDATE_TITLE": "Een regel bewerken"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Naam",
"DESCRIPTION": "Beschrijving",
"WHEN": "Wanneer",
"PERFORM_ACTIONS": "Acties uitvoeren",
"OPTIONS": "Andere opties"
},
"PLACEHOLDER": {
"NAME": "Regelnaam invoeren",
"DESCRIPTION": "Regelbeschrijving invoeren",
"NO_DESCRIPTION": "Geen beschrijving",
"VALUE": "Waarde",
"CHOOSE_FOLDER": "Doelmap kiezen"
},
"ERROR": {
"REQUIRED": "Dit veld is vereist",
"RULE_COMPOSITE_CONDITION_INVALID": "Eén of meer voorwaardengroepen zijn leeg",
"INSUFFICIENT_TRIGGERS_SELECTED": "Er is minstens één trigger vereist"
},
"TRIGGERS": {
"INBOUND": "Items worden gemaakt of gaan naar deze map",
"UPDATE": "Items worden bijgewerkt",
"OUTBOUND": "Items worden verwijderd of verlaten deze map"
},
"OPTIONS": {
"IS_INHERITABLE": "Regel is van toepassing op submappen",
"IS_ASYNCHRONOUS": "Regel op de achtergrond uitvoeren",
"DISABLE_RULE": "Regel uitschakelen",
"ERROR_SCRIPT": "Script uitvoeren wanneer er fouten optreden",
"NO_SCRIPT": "Geen"
},
"COMPARATORS": {
"EQUALS": "(=) Gelijk aan",
"CONTAINS": "Bevat",
"STARTS_WITH": "Begint met",
"ENDS_WITH": "Eindigt met",
"GREATER_THAN": "(>) Groter dan",
"LESS_THAN": "(<) Kleiner dan",
"GREATER_THAN_OR_EQUAL": "(>=) Groter dan of gelijk aan",
"LESS_THAN_OR_EQUAL": "(<=) Kleiner dan of gelijk aan",
"ON": "(=) Op",
"AFTER": "(>) Na",
"BEFORE": "(<) Voor",
"ON_OR_AFTER": "(>=) Op of na",
"ON_OR_BEFORE": "(<=) Op of voor",
"INSTANCE_OF": "(=) Is"
},
"FIELDS": {
"NAME": "Naam",
"SIZE": "Grootte",
"MIMETYPE": "Mimetype",
"ENCODING": "Codering",
"HAS_CATEGORY": "Heeft categorie",
"HAS_TAG": "Heeft tag",
"HAS_ASPECT": "Heeft aspect"
},
"LOGIC_OPERATORS": {
"IF": "Als",
"NOT_IF": "NIET Als",
"AND": "En",
"OR": "Of"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Voorwaarde toevoegen",
"ADD_GROUP": "Voorwaardengroep toevoegen",
"REMOVE": "Verwijderen"
},
"NO_CONDITIONS": "Geen voorwaarden",
"NO_CONDITIONS_IN_GROUP": "Geen voorwaarden in de groep",
"ACTION_BUTTONS": {
"ADD_ACTION": "Actie toevoegen",
"REMOVE": "Verwijderen"
},
"ACTION_SELECT_PLACEHOLDER": "Selecteer een actie"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regels"
},
"ACTIONS": {
"CREATE_RULE": "Een regel maken",
"LINK_RULES": "Regels koppelen",
"EDIT_RULE": "Bewerken",
"SEE_IN_FOLDER": "Zien in map",
"INHERIT_RULES": "Regels overnemen"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "De lijst is leeg",
"SUBTITLE": "Er zijn nog geen regels gedefinieerd voor deze map."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Regel verwijderen",
"MESSAGE": "Weet u zeker dat u deze regel wilt verwijderen?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Koppeling regelset verwijderen",
"MESSAGE": "Weet u zeker dat u de koppeling naar deze regelset wilt verwijderen?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regels uit huidige map",
"LINKED_RULES": "Regels uit gekoppelde map",
"INHERITED_RULES": "Overgenomen regels",
"LOAD_MORE_RULE_SETS": "Regels laden uit andere mappen",
"LOAD_MORE_RULES": "Meer regels laden",
"LOADING_RULES": "Regels worden geladen",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Overgenomen regels worden als eerste uitgevoerd",
"ALL_LINKED_RULES_ARE_DISABLED": "Alle regels die vanuit deze regelset zijn gekoppeld worden uitgeschakeld"
},
"LINK_RULES_DIALOG": {
"TITLE": "Selecteer een map van waaruit regels moeten worden gekoppeld",
"CANCEL": "Annuleren",
"SUBMIT": "Map selecteren",
"EMPTY_RULES_LIST": {
"TITLE": "Geen regels in eigendom",
"SUBTITLE": "De geselecteerde map bevat geen eigen regels."
},
"LIST_OF_RULES_TO_LINK": "Lijst met regels die worden gekoppeld",
"ERRORS": {
"REQUEST_FAILED": "Fout bij het maken van een koppeling naar een regelset"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Fout bij het verwijderen van een koppeling uit een regelset"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Zarządzaj regułami"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Anuluj",
"CREATE": "Utwórz",
"CREATE_TITLE": "Utwórz regułę",
"UPDATE": "Aktualizuj",
"UPDATE_TITLE": "Edytuj regułę"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nazwa",
"DESCRIPTION": "Opis",
"WHEN": "Kiedy",
"PERFORM_ACTIONS": "Wykonaj czynności",
"OPTIONS": "Inne opcje"
},
"PLACEHOLDER": {
"NAME": "Wprowadź nazwę reguły",
"DESCRIPTION": "Wprowadź opis reguły",
"NO_DESCRIPTION": "Brak opisu",
"VALUE": "Wartość",
"CHOOSE_FOLDER": "Wybierz folder docelowy"
},
"ERROR": {
"REQUIRED": "To pole jest wymagane.",
"RULE_COMPOSITE_CONDITION_INVALID": "Co najmniej jedna grupa warunków jest pusta",
"INSUFFICIENT_TRIGGERS_SELECTED": "Co najmniej jeden wyzwalacz jest wymagany"
},
"TRIGGERS": {
"INBOUND": "Pozycje są utworzone lub wejdź do tego folderu",
"UPDATE": "Pozycje są zaktualizowane",
"OUTBOUND": "Pozycje są usunięte lub opuść ten folder"
},
"OPTIONS": {
"IS_INHERITABLE": "Reguła ma zastosowanie do podfolderów",
"IS_ASYNCHRONOUS": "Uruchom regułę w tle",
"DISABLE_RULE": "Wyłącz regułę",
"ERROR_SCRIPT": "Jeśli wystąpią błędy, uruchom skrypt",
"NO_SCRIPT": "Brak"
},
"COMPARATORS": {
"EQUALS": "(=) Równa się",
"CONTAINS": "Zawiera",
"STARTS_WITH": "Zaczyna się na",
"ENDS_WITH": "Kończy się na",
"GREATER_THAN": "(>) Większe od",
"LESS_THAN": "(<) Mniejsze od",
"GREATER_THAN_OR_EQUAL": "(>=) Większe od lub równe",
"LESS_THAN_OR_EQUAL": "(<=) Mniejsze od lub równe",
"ON": "(=) W dniu",
"AFTER": "(>) Po",
"BEFORE": "(<) Przed",
"ON_OR_AFTER": "(>=) W dniu lub po",
"ON_OR_BEFORE": "(<=) W dniu lub przed",
"INSTANCE_OF": "(=) Jest"
},
"FIELDS": {
"NAME": "Nazwa",
"SIZE": "Rozmiar",
"MIMETYPE": "Typ MIME",
"ENCODING": "Kodowanie",
"HAS_CATEGORY": "Posiada kategorię",
"HAS_TAG": "Posiada znacznik",
"HAS_ASPECT": "Posiada aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Jeśli",
"NOT_IF": "NIE jeśli",
"AND": "Oraz",
"OR": "Lub"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Dodaj warunek",
"ADD_GROUP": "Dodaj grupę warunków",
"REMOVE": "Usuń"
},
"NO_CONDITIONS": "Brak warunków",
"NO_CONDITIONS_IN_GROUP": "Brak warunków w grupie",
"ACTION_BUTTONS": {
"ADD_ACTION": "Dodaj czynność",
"REMOVE": "Usuń"
},
"ACTION_SELECT_PLACEHOLDER": "Wybierz czynność"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "reguły"
},
"ACTIONS": {
"CREATE_RULE": "Utwórz regułę",
"LINK_RULES": "Połącz reguły",
"EDIT_RULE": "Edytuj",
"SEE_IN_FOLDER": "Zobacz w folderze",
"INHERIT_RULES": "Dziedzicz reguły"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Ta lista jest pusta",
"SUBTITLE": "Nie zdefiniowano jeszcze reguł dla tego folderu."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Usuń regułę",
"MESSAGE": "Czy na pewno chcesz usunąć tę regułę?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Usuń łącze zbioru reguł",
"MESSAGE": "Czy na pewno usunąć łącze do tego zbioru reguł?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Reguły z bieżącego folderu",
"LINKED_RULES": "Reguły z połączonego folderu",
"INHERITED_RULES": "Odziedziczone reguły",
"LOAD_MORE_RULE_SETS": "Wczytaj reguły z innych folderów",
"LOAD_MORE_RULES": "Wczytaj więcej reguł",
"LOADING_RULES": "Wczytywanie reguł",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Odziedziczone reguły zostaną wykonane najpierw",
"ALL_LINKED_RULES_ARE_DISABLED": "Wszystkie reguły z łączem z tego zbioru reguł są wyłączone"
},
"LINK_RULES_DIALOG": {
"TITLE": "Wybierz folder, z którego mają być łączone reguły",
"CANCEL": "Anuluj",
"SUBMIT": "Wybierz folder",
"EMPTY_RULES_LIST": {
"TITLE": "Brak własnych reguł",
"SUBTITLE": "Wybrany folder nie ma żadnych własnych reguł."
},
"LIST_OF_RULES_TO_LINK": "Lista reguł, które zostaną połączone",
"ERRORS": {
"REQUEST_FAILED": "Błąd podczas próby utworzenia łącza do zbioru reguł"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Błąd podczas próby usunięcia łącza ze zbioru reguł"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Gerenciar regras"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Cancelar",
"CREATE": "Criar",
"CREATE_TITLE": "Criar uma regra",
"UPDATE": "Atualizar",
"UPDATE_TITLE": "Editar uma regra"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Nome",
"DESCRIPTION": "Descrição",
"WHEN": "Quando",
"PERFORM_ACTIONS": "Realizar ações",
"OPTIONS": "Outras opções"
},
"PLACEHOLDER": {
"NAME": "Inserir um nome para a regra",
"DESCRIPTION": "Inserir uma descrição para a regra",
"NO_DESCRIPTION": "Nenhuma descrição",
"VALUE": "Valor",
"CHOOSE_FOLDER": "Escolher pasta de destino"
},
"ERROR": {
"REQUIRED": "Este campo é obrigatório",
"RULE_COMPOSITE_CONDITION_INVALID": "Um ou mais grupos de condições estiver vazio",
"INSUFFICIENT_TRIGGERS_SELECTED": "Pelo menos um gatilho será necessário"
},
"TRIGGERS": {
"INBOUND": "Itens foram criados ou inseridos nesta pasta",
"UPDATE": "Itens foram atualizados",
"OUTBOUND": "Itens foram excluídos ou saíram desta pasta"
},
"OPTIONS": {
"IS_INHERITABLE": "A regra é aplicável a subpastas",
"IS_ASYNCHRONOUS": "Executar regra no plano de fundo",
"DISABLE_RULE": "Desativar regra",
"ERROR_SCRIPT": "Se ocorrerem erros, execute o script",
"NO_SCRIPT": "Nenhum"
},
"COMPARATORS": {
"EQUALS": "(=) Igual a",
"CONTAINS": "Contém",
"STARTS_WITH": "Inicia com",
"ENDS_WITH": "Termina com",
"GREATER_THAN": "Maior que",
"LESS_THAN": "Menor que",
"GREATER_THAN_OR_EQUAL": "(>=) Maior que ou igual a",
"LESS_THAN_OR_EQUAL": "(<=) Menor que ou igual a",
"ON": "(=) Em",
"AFTER": "(>) Após",
"BEFORE": "(<) Antes",
"ON_OR_AFTER": "(>=) Em ou após",
"ON_OR_BEFORE": "(<=) Em ou antes",
"INSTANCE_OF": "(=) É"
},
"FIELDS": {
"NAME": "Nome",
"SIZE": "Tamanho",
"MIMETYPE": "Tipo mime",
"ENCODING": "Codificação",
"HAS_CATEGORY": "Tem categoria",
"HAS_TAG": "Tem marca",
"HAS_ASPECT": "Tem aspecto"
},
"LOGIC_OPERATORS": {
"IF": "Se",
"NOT_IF": "Não se",
"AND": "E",
"OR": "Ou"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Adicionar condição",
"ADD_GROUP": "Adicionar grupo de condição",
"REMOVE": "Excluir"
},
"NO_CONDITIONS": "Sem condições",
"NO_CONDITIONS_IN_GROUP": "Sem condições no grupo",
"ACTION_BUTTONS": {
"ADD_ACTION": "Adicionar ação",
"REMOVE": "Excluir"
},
"ACTION_SELECT_PLACEHOLDER": "Selecionar uma ação"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regras"
},
"ACTIONS": {
"CREATE_RULE": "Criar uma regra",
"LINK_RULES": "Regras de link",
"EDIT_RULE": "Editar",
"SEE_IN_FOLDER": "Consulte na pasta",
"INHERIT_RULES": "Herdar regras"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "A lista está vazia",
"SUBTITLE": "Não há regras definidas para esta pasta ainda."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Excluir regra",
"MESSAGE": "Tem certeza de que deseja excluir esta regra?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Excluir link do conjunto de regras",
"MESSAGE": "Tem certeza de que deseja excluir o link deste conjunto de regras?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regras da pasta atual",
"LINKED_RULES": "Regras da pasta vinculada",
"INHERITED_RULES": "Regras herdadas",
"LOAD_MORE_RULE_SETS": "Carregar regras de outras pastas",
"LOAD_MORE_RULES": "Carregar mais regras",
"LOADING_RULES": "Carregando regras",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "As regras herdadas serão executadas primeiro",
"ALL_LINKED_RULES_ARE_DISABLED": "Todas as regras vinculadas deste conjunto de regras estão desativadas"
},
"LINK_RULES_DIALOG": {
"TITLE": "Selecione uma pasta da qual vincular regras",
"CANCEL": "Cancelar",
"SUBMIT": "Selecionar pasta",
"EMPTY_RULES_LIST": {
"TITLE": "Nenhuma regra própria",
"SUBTITLE": "A pasta selecionada não possui regras próprias."
},
"LIST_OF_RULES_TO_LINK": "Lista das regras que serão vinculadas",
"ERRORS": {
"REQUEST_FAILED": "Erro ao tentar criar um link a um conjunto de regras"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Erro ao tentar excluir um link de um conjunto de regras"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Управление правилами"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Отмена",
"CREATE": "Создать",
"CREATE_TITLE": "Создать правило",
"UPDATE": "Обновить",
"UPDATE_TITLE": "Редактировать правило"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Имя",
"DESCRIPTION": "Описание",
"WHEN": "Когда",
"PERFORM_ACTIONS": "Выполнить действия",
"OPTIONS": "Другие параметры"
},
"PLACEHOLDER": {
"NAME": "Введите имя правила",
"DESCRIPTION": "Введите описание правила",
"NO_DESCRIPTION": "Нет описания",
"VALUE": "Значение",
"CHOOSE_FOLDER": "Выбрать расположение"
},
"ERROR": {
"REQUIRED": "Поле обязательно для заполнения",
"RULE_COMPOSITE_CONDITION_INVALID": "Одна или несколько групп условий пустые",
"INSUFFICIENT_TRIGGERS_SELECTED": "Требуется как минимум один триггер"
},
"TRIGGERS": {
"INBOUND": "Элементы создаются или попадают в эту папку",
"UPDATE": "Элементы обновлены",
"OUTBOUND": "Элементы удаляются или покидают эту папку"
},
"OPTIONS": {
"IS_INHERITABLE": "Правило применяется к вложенным папкам",
"IS_ASYNCHRONOUS": "Выполнить правило в фоновом режиме",
"DISABLE_RULE": "Отключить правило",
"ERROR_SCRIPT": "Запустите скрипт при возникновении ошибок",
"NO_SCRIPT": "Нет"
},
"COMPARATORS": {
"EQUALS": "(=) Равно",
"CONTAINS": "Содержит",
"STARTS_WITH": "Начинается с",
"ENDS_WITH": "Завершается с",
"GREATER_THAN": "(>) Больше чем",
"LESS_THAN": "(<) Меньше чем",
"GREATER_THAN_OR_EQUAL": "(>=) Больше или равно",
"LESS_THAN_OR_EQUAL": "(<=) Меньше или равно",
"ON": "(=) В",
"AFTER": "(>) После",
"BEFORE": "(<) До",
"ON_OR_AFTER": "(>=) В или после",
"ON_OR_BEFORE": "(<=) В или ранее",
"INSTANCE_OF": "(=) Есть"
},
"FIELDS": {
"NAME": "Имя",
"SIZE": "Размер",
"MIMETYPE": "Тип MIME",
"ENCODING": "Кодировка",
"HAS_CATEGORY": "Имеет категорию",
"HAS_TAG": "Имеется метка",
"HAS_ASPECT": "Имеет аспект"
},
"LOGIC_OPERATORS": {
"IF": "Если",
"NOT_IF": "Не если",
"AND": "И",
"OR": "Или"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Добавить условие",
"ADD_GROUP": "Добавить группу условий",
"REMOVE": "Удалить"
},
"NO_CONDITIONS": "Нет условий",
"NO_CONDITIONS_IN_GROUP": "В группе нет условий",
"ACTION_BUTTONS": {
"ADD_ACTION": "Добавить действие",
"REMOVE": "Удалить"
},
"ACTION_SELECT_PLACEHOLDER": "Выбрать действие"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "правила"
},
"ACTIONS": {
"CREATE_RULE": "Создать правило",
"LINK_RULES": "Привязать правила",
"EDIT_RULE": "Редактировать",
"SEE_IN_FOLDER": "Показать в папке",
"INHERIT_RULES": "Наследовать правила"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Список пуст",
"SUBTITLE": "Для этой папки еще не определены правила."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Удалить правило",
"MESSAGE": "Вы действительно хотите удалить это правило?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Удалить ссылку на набор правил",
"MESSAGE": "Вы действительно хотите удалить ссылку на этот набор правил?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Правила из текущей папки",
"LINKED_RULES": "Правила из привязанной папки",
"INHERITED_RULES": "Унаследованные правила",
"LOAD_MORE_RULE_SETS": "Загрузить правила из других папок",
"LOAD_MORE_RULES": "Загрузить другие правила",
"LOADING_RULES": "Загрузка правил",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Унаследованные правила будут выполняться в первую очередь",
"ALL_LINKED_RULES_ARE_DISABLED": "Все правила, привязанные из этого набора правил, отключены"
},
"LINK_RULES_DIALOG": {
"TITLE": "Выберите папку, из которой будут взяты правила",
"CANCEL": "Отмена",
"SUBMIT": "Выбрать папку",
"EMPTY_RULES_LIST": {
"TITLE": "Нет приобретенных правил",
"SUBTITLE": "Выбранная папка не имеет собственных правил."
},
"LIST_OF_RULES_TO_LINK": "Список правил, которые будут привязаны",
"ERRORS": {
"REQUEST_FAILED": "Ошибка при попытке создать ссылку на набор правил"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Ошибка при попытке удалить ссылку из набора правил"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "Hantera regler"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "Avbryt",
"CREATE": "Skapa",
"CREATE_TITLE": "Skapa en regel",
"UPDATE": "Uppdatera",
"UPDATE_TITLE": "Redigera en regel"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "Namn",
"DESCRIPTION": "Beskrivning",
"WHEN": "När",
"PERFORM_ACTIONS": "Utför åtgärder",
"OPTIONS": "Andra alternativ"
},
"PLACEHOLDER": {
"NAME": "Ange regelnamn",
"DESCRIPTION": "Ange regelbeskrivning",
"NO_DESCRIPTION": "Ingen beskrivning",
"VALUE": "Värde",
"CHOOSE_FOLDER": "Välj destinationsmapp"
},
"ERROR": {
"REQUIRED": "Det här fältet krävs",
"RULE_COMPOSITE_CONDITION_INVALID": "En eller flera villkorsgrupper är tomma",
"INSUFFICIENT_TRIGGERS_SELECTED": "Minst en utlösare krävs"
},
"TRIGGERS": {
"INBOUND": "Objekt skapas eller gå in i den här mappen",
"UPDATE": "Objekt uppdateras",
"OUTBOUND": "Objekt tas bort eller lämna den här mappen"
},
"OPTIONS": {
"IS_INHERITABLE": "Regel för undermappar",
"IS_ASYNCHRONOUS": "Kör regel i bakgrunden",
"DISABLE_RULE": "Inaktivera regel",
"ERROR_SCRIPT": "Kör skript om fel inträffar",
"NO_SCRIPT": "Ingen"
},
"COMPARATORS": {
"EQUALS": "(=) Är lika med",
"CONTAINS": "Innehåller",
"STARTS_WITH": "Börjar med",
"ENDS_WITH": "Slutar med",
"GREATER_THAN": "(>) Större än",
"LESS_THAN": "(<) Mindre än",
"GREATER_THAN_OR_EQUAL": "(>=) Större än eller lika med",
"LESS_THAN_OR_EQUAL": "(<=) Mindre än eller lika",
"ON": "(=) På",
"AFTER": "(>) Efter",
"BEFORE": "(<) Innan",
"ON_OR_AFTER": "(>=) På eller efter",
"ON_OR_BEFORE": "(<=) På eller innan",
"INSTANCE_OF": "(=) Är"
},
"FIELDS": {
"NAME": "Namn",
"SIZE": "Storlek",
"MIMETYPE": "Mime-typ",
"ENCODING": "Kodning",
"HAS_CATEGORY": "Har en kategori",
"HAS_TAG": "Har en tagg",
"HAS_ASPECT": "Har en aspekt"
},
"LOGIC_OPERATORS": {
"IF": "Om",
"NOT_IF": "INTE om",
"AND": "Och",
"OR": "Eller"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "Lägg till villkor",
"ADD_GROUP": "Lägg till villkorsgrupp",
"REMOVE": "Ta bort"
},
"NO_CONDITIONS": "Inga villkor",
"NO_CONDITIONS_IN_GROUP": "Inga villkor i gruppen",
"ACTION_BUTTONS": {
"ADD_ACTION": "Lägg till åtgärd",
"REMOVE": "Ta bort"
},
"ACTION_SELECT_PLACEHOLDER": "Välj en åtgärd"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "regler"
},
"ACTIONS": {
"CREATE_RULE": "Skapa en regel",
"LINK_RULES": "Länkregler",
"EDIT_RULE": "Redigera",
"SEE_IN_FOLDER": "Se i mappen",
"INHERIT_RULES": "Ärv regler"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "Listan är tom",
"SUBTITLE": "Det finns inga regler definierade för den här mappen ännu."
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "Radera regel",
"MESSAGE": "Vill du verkligen radera den här regeln?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "Ta bort regeluppsättningslänk",
"MESSAGE": "Är du säker på att du vill ta bort länken till denna regeluppsättning?"
}
},
"RULE_LIST": {
"OWNED_RULES": "Regler från aktuell mapp",
"LINKED_RULES": "Regler från länkad mapp",
"INHERITED_RULES": "Ärvda regler",
"LOAD_MORE_RULE_SETS": "Läs in regler från andra mappar",
"LOAD_MORE_RULES": "Ladda fler regler",
"LOADING_RULES": "Laddar regler",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "Ärvda regler körs först",
"ALL_LINKED_RULES_ARE_DISABLED": "Alla regler som är länkade från den här regeluppsättningen är inaktiverade"
},
"LINK_RULES_DIALOG": {
"TITLE": "Välj en mapp att länka regler från",
"CANCEL": "Avbryt",
"SUBMIT": "Välj mapp",
"EMPTY_RULES_LIST": {
"TITLE": "Inga ägda regler",
"SUBTITLE": "Den valda mappen har inga egna regler."
},
"LIST_OF_RULES_TO_LINK": "Lista över regler som kommer att länkas",
"ERRORS": {
"REQUEST_FAILED": "Det gick inte att skapa en länk till en regeluppsättning"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "Det gick inte att ta bort en länk från en regeluppsättning"
}
}
}
@@ -0,0 +1,144 @@
{
"ACA_FOLDER_RULES": {
"ACTIONS": {
"MANAGE_RULES": "管理规则"
},
"EDIT_RULE_DIALOG": {
"CANCEL": "取消",
"CREATE": "创建",
"CREATE_TITLE": "创建规则",
"UPDATE": "更新",
"UPDATE_TITLE": "编辑一个规则"
},
"RULE_DETAILS": {
"LABEL": {
"NAME": "名称",
"DESCRIPTION": "说明",
"WHEN": "什么时候",
"PERFORM_ACTIONS": "执行操作",
"OPTIONS": "其他选项"
},
"PLACEHOLDER": {
"NAME": "输入规则名称",
"DESCRIPTION": "输入规则描述",
"NO_DESCRIPTION": "没有描述",
"VALUE": "值",
"CHOOSE_FOLDER": "选择目标文件夹"
},
"ERROR": {
"REQUIRED": "此字段为必填字段",
"RULE_COMPOSITE_CONDITION_INVALID": "一个或多个条件组为空",
"INSUFFICIENT_TRIGGERS_SELECTED": "至少需要一个触发器"
},
"TRIGGERS": {
"INBOUND": "项目被创建或进入此文件夹",
"UPDATE": "项目被更新",
"OUTBOUND": "项目被删除或离开此文件夹"
},
"OPTIONS": {
"IS_INHERITABLE": "规则适用于子文件夹",
"IS_ASYNCHRONOUS": "在后台运行规则",
"DISABLE_RULE": "禁用规则",
"ERROR_SCRIPT": "如果发生错误则运行脚本",
"NO_SCRIPT": "无"
},
"COMPARATORS": {
"EQUALS": "(=) 等于",
"CONTAINS": "包含",
"STARTS_WITH": "开始于",
"ENDS_WITH": "结束于",
"GREATER_THAN": "(>) 大于",
"LESS_THAN": "(<) 小于",
"GREATER_THAN_OR_EQUAL": "(>=) 大于或等于",
"LESS_THAN_OR_EQUAL": "(<=) 小于或等于",
"ON": "(=) 当天",
"AFTER": "(>) 之后",
"BEFORE": "(<) 之后",
"ON_OR_AFTER": "(>=) 当天或之后",
"ON_OR_BEFORE": "(<=) 当天或之前",
"INSTANCE_OF": "(=) 是"
},
"FIELDS": {
"NAME": "名称",
"SIZE": "大小",
"MIMETYPE": "MIME 类型",
"ENCODING": "编码",
"HAS_CATEGORY": "有类别",
"HAS_TAG": "有标签",
"HAS_ASPECT": "有纵横比"
},
"LOGIC_OPERATORS": {
"IF": "如果",
"NOT_IF": "除非",
"AND": "与",
"OR": "或"
},
"CONDITION_BUTTONS": {
"ADD_CONDITION": "添加条件",
"ADD_GROUP": "添加条件组",
"REMOVE": "删除"
},
"NO_CONDITIONS": "无条件",
"NO_CONDITIONS_IN_GROUP": "组中无条件",
"ACTION_BUTTONS": {
"ADD_ACTION": "添加操作",
"REMOVE": "删除"
},
"ACTION_SELECT_PLACEHOLDER": "选择一个操作"
},
"MANAGE_RULES": {
"TOOLBAR": {
"BREADCRUMB": {
"RULES": "规则"
},
"ACTIONS": {
"CREATE_RULE": "创建规则",
"LINK_RULES": "链接规则",
"EDIT_RULE": "编辑",
"SEE_IN_FOLDER": "在文件夹中查看",
"INHERIT_RULES": "继承规则"
}
},
"EMPTY_RULES_LIST": {
"TITLE": "此列表为空",
"SUBTITLE": "尚未为此文件夹定义规则。"
}
},
"CONFIRMATION_DIALOG": {
"DELETE_RULE": {
"TITLE": "删除规则",
"MESSAGE": "是否确定要删除此规则?"
},
"DELETE_RULE_SET_LINK": {
"TITLE": "删除规则集链接",
"MESSAGE": "是否确定要删除此规则集的链接?"
}
},
"RULE_LIST": {
"OWNED_RULES": "来自当前文件夹的规则",
"LINKED_RULES": "来自链接文件夹的规则",
"INHERITED_RULES": "继承的规则",
"LOAD_MORE_RULE_SETS": "加载来自其他文件夹的规则",
"LOAD_MORE_RULES": "加载更多规则",
"LOADING_RULES": "正在加载规则",
"INHERITED_RULES_WILL_BE_RUN_FIRST": "首先运行继承的规则",
"ALL_LINKED_RULES_ARE_DISABLED": "禁用从此规则集链接的所有规则"
},
"LINK_RULES_DIALOG": {
"TITLE": "选择要从中链接规则的文件夹",
"CANCEL": "取消",
"SUBMIT": "选择文件夹",
"EMPTY_RULES_LIST": {
"TITLE": "无自定义规则",
"SUBTITLE": "所选文件夹不包含任何自定义规则。"
},
"LIST_OF_RULES_TO_LINK": "将链接的规则列表",
"ERRORS": {
"REQUEST_FAILED": "创建规则集链接时出错"
}
},
"ERRORS": {
"DELETE_RULE_SET_LINK_FAILED": "删除规则集链接时出错"
}
}
}
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/public-api.ts"
}
}
@@ -0,0 +1,91 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { CoreModule, TranslationService } from '@alfresco/adf-core';
import { ExtensionService, ExtensionsModule, provideExtensionConfig } from '@alfresco/adf-extensions';
import { NgModule } from '@angular/core';
import * as rules from './folder-rules.rules';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { EditRuleDialogUiComponent } from './rule-details/edit-rule-dialog.ui-component';
import { ManageRulesSmartComponent } from './manage-rules/manage-rules.smart-component';
import { RuleCompositeConditionUiComponent } from './rule-details/conditions/rule-composite-condition.ui-component';
import { RuleDetailsUiComponent } from './rule-details/rule-details.ui-component';
import { RuleSimpleConditionUiComponent } from './rule-details/conditions/rule-simple-condition.ui-component';
import { GenericErrorComponent, PageLayoutModule } from '@alfresco/aca-shared';
import { BreadcrumbModule, ContentNodeSelectorModule, DocumentListModule } from '@alfresco/adf-content-services';
import { RuleListItemUiComponent } from './rule-list/rule-list-item/rule-list-item.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 { RuleListUiComponent } from './rule-list/rule-list/rule-list.ui-component';
import { RuleSetPickerSmartComponent } from './rule-set-picker/rule-set-picker.smart-component';
const routes: Routes = [
{
path: 'rules',
component: ManageRulesSmartComponent
}
];
@NgModule({
providers: [provideExtensionConfig(['folder-rules.plugin.json'])],
imports: [
CommonModule,
RouterModule.forChild(routes),
CoreModule.forChild(),
PageLayoutModule,
BreadcrumbModule,
DocumentListModule,
ExtensionsModule,
ContentNodeSelectorModule,
GenericErrorComponent
],
declarations: [
EditRuleDialogUiComponent,
ManageRulesSmartComponent,
RuleActionListUiComponent,
RuleActionUiComponent,
RuleCompositeConditionUiComponent,
RuleDetailsUiComponent,
RuleListGroupingUiComponent,
RuleListItemUiComponent,
RuleListUiComponent,
RuleSetPickerSmartComponent,
RuleSimpleConditionUiComponent,
RuleTriggersUiComponent,
RuleOptionsUiComponent
]
})
export class AcaFolderRulesModule {
constructor(translation: TranslationService, extensions: ExtensionService) {
translation.addTranslationFolder('folder-rules', 'assets/folder-rules');
extensions.setEvaluators({
'rules.canManageFolderRules': rules.canManageFolderRules
});
}
}
@@ -0,0 +1,85 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AcaRuleContext } from '@alfresco/aca-shared/rules';
import { isFolderRulesEnabled, canManageFolderRules } from './folder-rules.rules';
describe('Folder Rules Visibility Rules', () => {
describe('isFolderRulesEnabled', () => {
it('should have the feature enabled', () => {
const context: any = {
appConfig: {
get: () => true
}
};
const result = isFolderRulesEnabled(context);
expect(result).toEqual(true);
});
it('should have the feature disabled', () => {
const context: any = {
appConfig: {
get: () => false
}
};
const result = isFolderRulesEnabled(context);
expect(result).toEqual(false);
});
});
describe('canManageFolderRules', () => {
let context: AcaRuleContext;
beforeEach(() => {
context = {
appConfig: {
get: () => true
},
selection: {
folder: {} as any
},
navigation: {
url: '/personal-files'
},
permissions: {
check: () => true
}
} as any;
});
it('should allow creating a rule for the selected folder', () => {
const result = canManageFolderRules(context);
expect(result).toEqual(true);
});
it('should not allow creating a rule if no folder selected', () => {
context.selection.folder = null;
const result = canManageFolderRules(context);
expect(result).toEqual(false);
});
});
});
@@ -0,0 +1,31 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AcaRuleContext, hasFolderSelected, canEditFolder, isNotFavorites } from '@alfresco/aca-shared/rules';
export const isFolderRulesEnabled = (context: AcaRuleContext) => context.appConfig.get<boolean>('plugins.folderRules', false);
export const isFolderRulesAllowed = (context: AcaRuleContext) =>
isFolderRulesEnabled(context) && canEditFolder(context) && hasFolderSelected(context) && isNotFavorites(context);
export const canManageFolderRules = (context: AcaRuleContext): boolean => isFolderRulesAllowed(context);
@@ -0,0 +1,140 @@
<aca-page-layout>
<aca-page-layout-header>
<adf-toolbar class="adf-toolbar--inline">
<button mat-icon-button (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</button>
</adf-toolbar>
<adf-breadcrumb root="{{'ACA_FOLDER_RULES.ACTIONS.MANAGE_RULES' | translate}}"></adf-breadcrumb>
</aca-page-layout-header>
<aca-page-layout-content>
<div class="main-content">
<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>
<mat-slide-toggle
data-automation-id="manage-rules-inheritance-toggle-button"
[checked]="isInheritanceEnabled"
(change)="onInheritanceToggleChange($event)"
[disabled]="isInheritanceToggleDisabled"
[labelPosition]="'before'">
{{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.INHERIT_RULES' | translate }}
</mat-slide-toggle>
<mat-divider vertical class="vertical-divider"></mat-divider>
<div class="aca-manage-rules__actions-bar__buttons">
<button
*ngIf="!(mainRuleSet$ | async)"
data-automation-id="manage-rules-link-button"
mat-stroked-button
(click)="openLinkRulesDialog()">
{{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.LINK_RULES' | translate }}
</button>
<button
*ngIf="canEditMainRule"
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>
</div>
</adf-toolbar>
<mat-divider></mat-divider>
<div class="aca-manage-rules__container" *ngIf="isMainRuleSetNotEmpty || isInheritedRuleSetsNotEmpty; else emptyContent">
<aca-rule-list
[mainRuleSet]="mainRuleSet$ | async"
[folderId]="nodeId"
[inheritedRuleSets]="inheritedRuleSets$ | async"
[hasMoreRuleSets]="hasMoreRuleSets$ | async"
[ruleSetsLoading]="ruleSetsLoading$ | async"
[selectedRule]="selectedRule$ | async"
(loadMoreRuleSets)="onLoadMoreRuleSets()"
(loadMoreRules)="onLoadMoreRules($event)"
(selectRule)="onSelectRule($event)"
(ruleEnabledChanged)="onRuleEnabledToggle($event[0], $event[1])"
(ruleSetEditLinkClicked)="openLinkRulesDialog($event)"
(ruleSetUnlinkClicked)="onRuleSetUnlinkClicked($event)">
</aca-rule-list>
<div class="aca-manage-rules__container__rule-details">
<div class="aca-manage-rules__container__rule-details__header" *ngIf="(selectedRule$ | async) as selectedRule">
<div class="aca-manage-rules__container__rule-details__header__title">
<div class="aca-manage-rules__container__rule-details__header__title__name">
{{ selectedRule.name }}
</div>
<div class="aca-manage-rules__container__rule-details__header__title__description">
{{ selectedRule.description }}
</div>
</div>
<div class="aca-manage-rules__container__rule-details__header__buttons">
<ng-container *ngIf="canEditSelectedRule; 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>
<div class="aca-manage-rules__container__rule-details__content" *ngIf="(selectedRule$ | async) as selectedRule">
<aca-rule-details
[actionDefinitions]="actionDefinitions$ | async"
[readOnly]="true"
[preview]="true"
[value]="selectedRule"
[nodeId]="nodeId">
</aca-rule-details>
</div>
</div>
</div>
<ng-template #emptyContent>
<adf-empty-content
icon="library_books"
[title]="'ACA_FOLDER_RULES.MANAGE_RULES.EMPTY_RULES_LIST.TITLE' | translate"
[subtitle]="'ACA_FOLDER_RULES.MANAGE_RULES.EMPTY_RULES_LIST.SUBTITLE' | translate"
>
</adf-empty-content>
</ng-template>
</ng-container>
<ng-template #genericError>
<aca-page-layout-error>
<aca-generic-error></aca-generic-error>
</aca-page-layout-error>
</ng-template>
</ng-template>
</div>
</aca-page-layout-content>
</aca-page-layout>
@@ -0,0 +1,103 @@
.aca-manage-rules {
&__actions-bar {
padding: 0 30px;
display: flex;
align-items: center;
&__title {
align-items: center;
width: unset !important;
flex: 1;
&__breadcrumb {
margin-left: 18px;
}
}
&__buttons {
display: flex;
align-items: stretch;
gap: 12px;
}
.mat-slide-toggle-label {
font-weight: 600;
font-size: 14px;
color: var(--adf-breadcrumb-item-active-color);
}
.vertical-divider {
height: 50%;
margin: 0 12px;
}
}
&__container {
display: grid;
grid-template-columns: minmax(250px, 1fr) 3fr;
padding: 20px;
gap: 12px;
overflow-y: auto;
flex: 1;
&__rule-details {
border: 1px solid var(--theme-border-color);
border-radius: 12px;
overflow-y: auto;
&__header {
position: sticky;
top: 0;
z-index: 1;
display: flex;
flex-direction: row;
align-items: flex-start;
overflow-x: auto;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid var(--theme-border-color);
background-color: var(--theme-background-color);
column-gap: 20px;
&__title {
display: flex;
flex-direction: column;
gap: 4px;
word-wrap: break-word;
min-width: 100px;
&__name {
font-size: 1.2em;
font-weight: bold;
}
&__description {
font-size: 0.8em;
font-style: italic;
}
}
&__buttons {
display: flex;
flex-direction: row;
align-items: stretch;
gap: 4px;
button {
color: var(--theme-text-color);
&#delete-rule-btn {
padding: 0 8px;
min-width: unset;
}
.mat-icon {
// Something pops out of this button for some reason so this is necessary
overflow: hidden !important;
}
}
}
}
}
}
}
@@ -0,0 +1,341 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AcaFolderRulesModule } from '../folder-rules.module';
import { ManageRulesSmartComponent } from './manage-rules.smart-component';
import { DebugElement, Predicate } from '@angular/core';
import { CoreTestingModule } from '@alfresco/adf-core';
import { FolderRulesService } from '../services/folder-rules.service';
import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, of, Subject } from 'rxjs';
import {
inheritedRuleSetMock,
inheritedRuleSetWithEmptyRulesMock,
inheritedRuleSetWithOnlyDisabledRulesMock,
ownedRuleSetMock,
ruleSetWithLinkMock
} from '../mock/rule-sets.mock';
import { By } from '@angular/platform-browser';
import { getOwningFolderEntryMock, owningFolderIdMock, owningFolderMock } from '../mock/node.mock';
import { MatDialog } from '@angular/material/dialog';
import { ActionsService } from '../services/actions.service';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { ruleMock, ruleSettingsMock } from '../mock/rules.mock';
import { Store } from '@ngrx/store';
import { AppService } from '@alfresco/aca-shared';
describe('ManageRulesSmartComponent', () => {
let fixture: ComponentFixture<ManageRulesSmartComponent>;
let component: ManageRulesSmartComponent;
let debugElement: DebugElement;
let folderRuleSetsService: FolderRuleSetsService;
let folderRulesService: FolderRulesService;
let actionsService: ActionsService;
let callApiSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule, AcaFolderRulesModule],
providers: [
{
provide: AppService,
useValue: {
appNavNarMode$: new BehaviorSubject('expanded'),
toggleAppNavBar$: new Subject()
}
},
FolderRuleSetsService,
FolderRulesService,
{ provide: Store, useValue: { dispatch: () => {} } },
{ provide: ActivatedRoute, useValue: { params: of({ nodeId: owningFolderIdMock }) } }
]
});
fixture = TestBed.createComponent(ManageRulesSmartComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
folderRulesService = TestBed.inject(FolderRulesService);
actionsService = TestBed.inject(ActionsService);
spyOn(actionsService, 'loadActionDefinitions').and.stub();
spyOn(folderRulesService, 'getRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock));
callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi');
callApiSpy
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${owningFolderIdMock}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${owningFolderIdMock}?include=path%2Cproperties%2CallowableOperations%2Cpermissions`, 'GET')
.and.returnValue(Promise.resolve(getOwningFolderEntryMock));
});
afterEach(() => {
fixture.destroy();
});
it('should show a list of rule sets and rules', () => {
const loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.stub();
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
folderRuleSetsService.isLoading$ = of(false);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
actionsService.loading$ = of(false);
fixture.detectChanges();
expect(component).toBeTruthy();
expect(loadRuleSetsSpy).toHaveBeenCalledOnceWith(component.nodeId);
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(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 show adf-empty-content if node has no rules defined yet', () => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetWithEmptyRulesMock]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
fixture.detectChanges();
expect(component).toBeTruthy();
const adfEmptyContent = debugElement.query(By.css('adf-empty-content'));
const ruleSets = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(adfEmptyContent).toBeTruthy();
expect(ruleSets.length).toBe(0);
expect(ruleDetails).toBeFalsy();
});
it('should show adf-empty-content if there are only inherited disabled rules', () => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetWithOnlyDisabledRulesMock]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
fixture.detectChanges();
expect(component).toBeTruthy();
const adfEmptyContent = debugElement.query(By.css('adf-empty-content'));
const ruleSets = debugElement.queryAll(By.css(`[data-automation-id="rule-set-list-item"]`));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(adfEmptyContent).toBeTruthy();
expect(ruleSets.length).toBe(0);
expect(ruleDetails).toBeFalsy();
});
it('should only show aca-generic-error if the non-existing node was provided', () => {
folderRuleSetsService.folderInfo$ = of(null);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
fixture.detectChanges();
expect(component).toBeTruthy();
const acaGenericError = debugElement.query(By.css('aca-generic-error'));
const rules = debugElement.query(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(acaGenericError).toBeTruthy();
expect(rules).toBeFalsy();
expect(ruleDetails).toBeFalsy();
});
it('should only show progress bar while loading', async () => {
folderRuleSetsService.folderInfo$ = of(null);
folderRuleSetsService.mainRuleSet$ = of(null);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(true);
actionsService.loading$ = of(true);
fixture.detectChanges();
expect(component).toBeTruthy();
const matProgressBar = debugElement.query(By.css('mat-progress-bar'));
const rules = debugElement.query(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
expect(matProgressBar).toBeTruthy();
expect(rules).toBeFalsy();
expect(ruleDetails).toBeFalsy();
});
it('should call deleteRule() if confirmation dialog returns true', () => {
const dialog = TestBed.inject(MatDialog);
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
folderRuleSetsService.inheritedRuleSets$ = of([inheritedRuleSetMock]);
folderRuleSetsService.isLoading$ = of(false);
folderRulesService.selectedRule$ = of(ruleMock('owned-rule-1'));
folderRulesService.deletedRuleId$ = of(null);
actionsService.loading$ = of(false);
const onRuleDeleteButtonClickedSpy = spyOn(component, 'onRuleDeleteButtonClicked').and.callThrough();
const dialogResult: any = {
afterClosed: () => of(true)
};
const dialogOpenSpy = spyOn(dialog, 'open').and.returnValue(dialogResult);
const deleteRuleSpy = spyOn(folderRulesService, 'deleteRule');
const onRuleDeleteSpy = spyOn(component, 'onRuleDelete').and.callThrough();
fixture.detectChanges();
expect(component).toBeTruthy('expected component');
const rules = debugElement.queryAll(By.css('.aca-rule-list-item'));
const ruleDetails = debugElement.query(By.css('aca-rule-details'));
const deleteRuleBtn = fixture.debugElement.nativeElement.querySelector('#delete-rule-btn');
deleteRuleBtn.click();
fixture.detectChanges();
folderRulesService.deletedRuleId$ = of('owned-rule-1-id');
expect(onRuleDeleteButtonClickedSpy).toHaveBeenCalled();
expect(dialogOpenSpy).toHaveBeenCalled();
expect(deleteRuleSpy).toHaveBeenCalled();
expect(onRuleDeleteSpy).toHaveBeenCalledTimes(1);
expect(rules).toBeTruthy('expected rules');
expect(ruleDetails).toBeTruthy('expected ruleDetails');
expect(deleteRuleBtn).toBeTruthy();
});
describe('Create rule & link rules buttons visibility', () => {
let createButtonPredicate: Predicate<DebugElement>;
let linkButtonPredicate: Predicate<DebugElement>;
beforeEach(() => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
createButtonPredicate = By.css(`[data-automation-id="manage-rules-create-button"]`);
linkButtonPredicate = By.css(`[data-automation-id="manage-rules-link-button"]`);
});
it('should show the create rule button if there is no main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(null);
fixture.detectChanges();
const createButton = debugElement.query(createButtonPredicate);
expect(createButton).toBeTruthy();
});
it('should show the link rules button if there is no main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(null);
fixture.detectChanges();
const linkButton = debugElement.query(linkButtonPredicate);
expect(linkButton).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(createButtonPredicate);
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(createButtonPredicate);
expect(createButton).toBeFalsy();
});
it('should not show the link rules button if the folder has a main rule set', () => {
folderRuleSetsService.mainRuleSet$ = of(ownedRuleSetMock);
fixture.detectChanges();
const linkButton = debugElement.query(linkButtonPredicate);
expect(linkButton).toBeFalsy();
});
});
describe('Rule inheritance toggle button', () => {
beforeEach(() => {
folderRuleSetsService.folderInfo$ = of(owningFolderMock);
folderRuleSetsService.inheritedRuleSets$ = of([]);
folderRuleSetsService.isLoading$ = of(false);
actionsService.loading$ = of(false);
});
it('should show inherit rules toggle button, and disable it when isInheritanceToggleDisabled = true', () => {
fixture.detectChanges();
const createButton = debugElement.query(By.css(`[data-automation-id="manage-rules-inheritance-toggle-button"]`));
expect(createButton).toBeTruthy();
component.isInheritanceToggleDisabled = true;
fixture.detectChanges();
expect(createButton.nativeNode.classList).toContain('mat-disabled');
});
it('should call onInheritanceToggleChange() on change', () => {
const onInheritanceToggleChangeSpy = spyOn(component, 'onInheritanceToggleChange').and.callThrough();
const updateRuleSettingsSpy = spyOn(folderRulesService, 'updateRuleSettings').and.returnValue(Promise.resolve(ruleSettingsMock));
const loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.callThrough();
fixture.detectChanges();
const inheritanceToggleBtn = fixture.debugElement.query(By.css(`[data-automation-id="manage-rules-inheritance-toggle-button"]`));
inheritanceToggleBtn.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(onInheritanceToggleChangeSpy).toHaveBeenCalled();
expect(updateRuleSettingsSpy).toHaveBeenCalledTimes(1);
expect(loadRuleSetsSpy).toHaveBeenCalledTimes(1);
});
});
});
@@ -0,0 +1,272 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
import { FolderRulesService } from '../services/folder-rules.service';
import { Observable, Subject, Subscription } from 'rxjs';
import { Rule } from '../model/rule.model';
import { ActivatedRoute } from '@angular/router';
import { NodeInfo } from '@alfresco/aca-shared/store';
import { delay, takeUntil } from 'rxjs/operators';
import { EditRuleDialogUiComponent } from '../rule-details/edit-rule-dialog.ui-component';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '@alfresco/adf-content-services';
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 { RuleSet } from '../model/rule-set.model';
import { RuleSetPickerSmartComponent } from '../rule-set-picker/rule-set-picker.smart-component';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
@Component({
selector: 'aca-manage-rules',
templateUrl: 'manage-rules.smart-component.html',
styleUrls: ['manage-rules.smart-component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.Default,
host: { class: 'aca-manage-rules' }
})
export class ManageRulesSmartComponent implements OnInit, OnDestroy {
nodeId = '';
isInheritanceEnabled = true;
isInheritanceToggleDisabled = false;
mainRuleSet$: Observable<RuleSet>;
inheritedRuleSets$: Observable<RuleSet[]>;
selectedRule$: Observable<Rule>;
selectedRuleSet$: Observable<RuleSet>;
hasMoreRuleSets$: Observable<boolean>;
ruleSetsLoading$: Observable<boolean>;
folderInfo$: Observable<NodeInfo>;
actionsLoading$: Observable<boolean>;
actionDefinitions$: Observable<ActionDefinitionTransformed[]>;
parameterConstraints$: Observable<ActionParameterConstraint[]>;
canEditMainRule = false;
canEditSelectedRule = false;
isMainRuleSetNotEmpty = false;
isInheritedRuleSetsNotEmpty = false;
private destroyed$ = new Subject<void>();
private _actionDefinitionsSub: Subscription;
constructor(
private location: Location,
private folderRulesService: FolderRulesService,
private route: ActivatedRoute,
private matDialogService: MatDialog,
private notificationService: NotificationService,
private actionsService: ActionsService,
private folderRuleSetsService: FolderRuleSetsService
) {}
ngOnInit() {
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$;
this.actionsLoading$ = this.actionsService.loading$.pipe(delay(0));
this.actionDefinitions$ = this.actionsService.actionDefinitionsListing$;
this.parameterConstraints$ = this.actionsService.parameterConstraints$;
this.folderRulesService.deletedRuleId$.pipe(takeUntil(this.destroyed$)).subscribe((deletedRuleId) => this.onRuleDelete(deletedRuleId));
this.actionsService.loadActionDefinitions();
this.route.params.subscribe((params) => {
this.nodeId = params.nodeId;
if (this.nodeId) {
this.folderRulesService.getRuleSettings(this.nodeId).then((ruleSettings) => {
this.isInheritanceEnabled = ruleSettings.value;
});
this.folderRuleSetsService.loadRuleSets(this.nodeId);
}
});
this._actionDefinitionsSub = this.actionDefinitions$.subscribe((actionDefinitions: ActionDefinitionTransformed[]) =>
this.actionsService.loadActionParameterConstraints(actionDefinitions)
);
this.mainRuleSet$.pipe(takeUntil(this.destroyed$)).subscribe((ruleSet) => {
this.canEditMainRule = this.canEditRule(ruleSet);
this.isMainRuleSetNotEmpty = !!ruleSet;
});
this.inheritedRuleSets$.pipe(takeUntil(this.destroyed$)).subscribe((inheritedRuleSet) => {
this.isInheritedRuleSetsNotEmpty = inheritedRuleSet.some((ruleSet) => ruleSet.rules.some((rule: Rule) => rule.isEnabled));
});
this.selectedRuleSet$.pipe(takeUntil(this.destroyed$)).subscribe((ruleSet) => {
this.canEditSelectedRule = this.canEditRule(ruleSet);
});
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
this._actionDefinitionsSub.unsubscribe();
}
goBack(): void {
this.location.back();
}
onSelectRule(rule: Rule) {
this.folderRulesService.selectRule(rule);
}
openCreateUpdateRuleDialog(model = {}) {
const dialogRef = this.matDialogService.open(EditRuleDialogUiComponent, {
width: '90%',
panelClass: 'aca-edit-rule-dialog-container',
data: {
model,
nodeId: this.nodeId,
parameterConstraints$: this.parameterConstraints$,
actionDefinitions$: this.actionDefinitions$
}
});
this.onSubmitRuleDialog(dialogRef);
}
onSubmitRuleDialog(dialogRef) {
dialogRef.componentInstance.submitted.subscribe(async (rule) => {
try {
if (rule.id) {
await this.onRuleUpdate(rule);
} else {
await this.onRuleCreate(rule);
}
dialogRef.close();
} catch (error) {
this.notificationService.showError(error.response.body.error.errorKey);
}
});
}
async onRuleUpdate(rule: Rule) {
const newRule = await this.folderRulesService.updateRule(this.nodeId, rule.id, rule);
this.folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
}
async onRuleCreate(ruleCreateParams: Partial<Rule>) {
const newRule = await this.folderRulesService.createRule(this.nodeId, ruleCreateParams);
this.folderRuleSetsService.addOrUpdateRuleInMainRuleSet(newRule);
}
async onRuleEnabledToggle(rule: Rule, isEnabled: boolean) {
await this.folderRulesService.updateRule(this.nodeId, rule.id, { ...rule, isEnabled });
}
async onInheritanceToggleChange(event: MatSlideToggleChange) {
this.isInheritanceToggleDisabled = true;
const ruleSettings = await this.folderRulesService.updateRuleSettings(this.nodeId, '-isInheritanceEnabled-', { value: event.checked });
this.isInheritanceEnabled = ruleSettings.value;
this.folderRuleSetsService.loadRuleSets(this.nodeId);
this.isInheritanceToggleDisabled = false;
}
onRuleDeleteButtonClicked(rule: Rule) {
this.matDialogService
.open(ConfirmDialogComponent, {
data: {
title: 'ACA_FOLDER_RULES.CONFIRMATION_DIALOG.DELETE_RULE.TITLE',
message: 'ACA_FOLDER_RULES.CONFIRMATION_DIALOG.DELETE_RULE.MESSAGE'
},
minWidth: '346px'
})
.afterClosed()
.subscribe((result) => {
if (result) {
this.folderRulesService.deleteRule(this.nodeId, rule.id);
}
});
}
onRuleDelete(deletedRuleId: string) {
this.folderRuleSetsService.removeRuleFromMainRuleSet(deletedRuleId);
}
onLoadMoreRuleSets() {
this.folderRuleSetsService.loadMoreInheritedRuleSets();
}
onLoadMoreRules(ruleSet: RuleSet) {
this.folderRulesService.loadRules(ruleSet);
}
canEditRule(ruleSet: RuleSet): boolean {
return !ruleSet || FolderRuleSetsService.isOwnedRuleSet(ruleSet, this.nodeId);
}
openLinkRulesDialog(existingRuleSet?: RuleSet) {
this.matDialogService
.open(RuleSetPickerSmartComponent, {
width: '90%',
panelClass: 'aca-rule-set-picker-container',
data: {
nodeId: this.nodeId,
defaultNodeId: this.nodeId,
existingRuleSet
}
})
.afterClosed()
.subscribe((result) => {
if (result) {
this.folderRuleSetsService.refreshMainRuleSet();
}
});
}
onRuleSetUnlinkClicked(linkedRuleSet: RuleSet) {
this.matDialogService
.open(ConfirmDialogComponent, {
data: {
title: 'ACA_FOLDER_RULES.CONFIRMATION_DIALOG.DELETE_RULE_SET_LINK.TITLE',
message: 'ACA_FOLDER_RULES.CONFIRMATION_DIALOG.DELETE_RULE_SET_LINK.MESSAGE'
},
minWidth: '346px'
})
.afterClosed()
.subscribe(async (result) => {
if (result) {
try {
await this.folderRuleSetsService.deleteRuleSetLink(this.nodeId, linkedRuleSet.id);
this.folderRuleSetsService.refreshMainRuleSet();
} catch (error) {
this.notificationService.showError('ACA_FOLDER_RULES.ERRORS.DELETE_RULE_SET_LINK_FAILED');
}
}
});
}
}
@@ -0,0 +1,64 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
export const dummyConstraints: ActionParameterConstraint[] = [
{
name: 'aspect-name',
constraints: [
{
value: 'cm:aspect1',
label: 'Label 1'
},
{
value: 'cm:aspect2',
label: 'Label 2'
},
{
value: 'cm:aspect3',
label: ''
}
]
}
];
export const rawConstraints = {
entry: {
constraintValues: [
{
value: 'cm:aspect1',
label: 'Label 1'
},
{
value: 'cm:aspect2',
label: 'Label 2'
},
{
value: 'cm:aspect3'
}
],
constraintName: 'ac-aspects'
}
};
@@ -0,0 +1,221 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ActionDefinitionTransformed, ActionParameterDefinitionTransformed, RuleAction } from '../model/rule-action.model';
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
export const actionDefListMock = {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
applicableTypes: [],
parameterDefinitions: [
{
name: 'mock-action-parameter-text',
type: 'd:text',
multiValued: false,
mandatory: true,
displayLabel: 'Mock action parameter text'
},
{
name: 'mock-action-parameter-boolean',
type: 'd:boolean',
multiValued: false,
mandatory: false
},
{
name: 'aspect-name',
type: 'd:qname',
multiValued: false,
mandatory: false,
parameterConstraintName: 'ac-aspects'
},
{
name: 'mock-action-parameter-noderef',
type: 'd:noderef',
multiValued: false,
mandatory: false
},
{
name: 'aspect-name',
type: 'd:noderef',
multiValued: false,
mandatory: false
}
],
name: 'mock-action-1-definition',
trackStatus: false,
description: 'This is a mock action',
id: 'mock-action-1-definition',
title: 'Action 1 title'
},
{
applicableTypes: [],
name: 'mock-action-2-definition',
trackStatus: false,
id: 'mock-action-2-definition'
}
]
}
};
const actionParam1TransformedMock: ActionParameterDefinitionTransformed = {
name: 'mock-action-parameter-text',
type: 'd:text',
multiValued: false,
mandatory: true,
displayLabel: 'Mock action parameter text',
parameterConstraintName: ''
};
const actionParam2TransformedMock: ActionParameterDefinitionTransformed = {
name: 'mock-action-parameter-boolean',
type: 'd:boolean',
multiValued: false,
mandatory: false,
displayLabel: 'mock-action-parameter-boolean',
parameterConstraintName: ''
};
const actionParam3TransformedMock: ActionParameterDefinitionTransformed = {
name: 'aspect-name',
type: 'd:qname',
multiValued: false,
mandatory: false,
displayLabel: 'aspect-name',
parameterConstraintName: 'ac-aspects'
};
const actionParam4TransformedMock: ActionParameterDefinitionTransformed = {
name: 'mock-action-parameter-noderef',
type: 'd:noderef',
multiValued: false,
mandatory: false,
displayLabel: 'mock-action-parameter-noderef',
parameterConstraintName: ''
};
const actionParam5TransformedMock: ActionParameterDefinitionTransformed = {
name: 'aspect-name',
type: 'd:noderef',
multiValued: false,
mandatory: false,
displayLabel: 'aspect-name',
parameterConstraintName: ''
};
const actionParamLinkToCategoryTransformedMock = {
name: 'category-value',
type: 'd:noderef',
multiValued: false,
mandatory: true,
displayLabel: 'Category value'
};
const action1TransformedMock: ActionDefinitionTransformed = {
id: 'mock-action-1-definition',
name: 'mock-action-1-definition',
description: 'This is a mock action',
title: 'Action 1 title',
applicableTypes: [],
trackStatus: false,
parameterDefinitions: [
actionParam1TransformedMock,
actionParam2TransformedMock,
actionParam3TransformedMock,
actionParam4TransformedMock,
actionParam5TransformedMock
]
};
const action2TransformedMock: ActionDefinitionTransformed = {
id: 'mock-action-2-definition',
name: 'mock-action-2-definition',
description: '',
title: 'mock-action-2-definition',
applicableTypes: [],
trackStatus: false,
parameterDefinitions: []
};
export const actionLinkToCategoryTransformedMock: ActionDefinitionTransformed = {
id: 'mock-action-3-definition',
name: 'mock-action-3-definition',
description: '',
title: 'mock-action-3-definition',
applicableTypes: [],
trackStatus: false,
parameterDefinitions: [actionParamLinkToCategoryTransformedMock]
};
export const actionsTransformedListMock: ActionDefinitionTransformed[] = [action1TransformedMock, action2TransformedMock];
export const validActionMock: RuleAction = {
actionDefinitionId: 'mock-action-1-definition',
params: {
'mock-action-parameter-text': 'mock'
}
};
export const nonExistentActionDefinitionIdMock: RuleAction = {
actionDefinitionId: 'non-existent-action-definition-id',
params: {}
};
export const missingMandatoryParameterMock: RuleAction = {
actionDefinitionId: 'mock-action-1-definition',
params: {}
};
export const incompleteMandatoryParameterMock: RuleAction = {
actionDefinitionId: 'mock-action-1-definition',
params: {
'mock-action-parameter-text': ''
}
};
export const validActionsMock: RuleAction[] = [
validActionMock,
{
actionDefinitionId: 'mock-action-2-definition',
params: {}
}
];
export const errorScriptConstraintMock: ActionParameterConstraint = {
name: 'script-ref',
constraints: [
{
value: 'script-1-value',
label: 'Script 1'
},
{
value: 'script-2-value',
label: 'Script 2'
}
]
};
@@ -0,0 +1,85 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleCompositeCondition } from '../model/rule-composite-condition.model';
import { RuleSimpleCondition } from '../model/rule-simple-condition.model';
const simpleConditionMock: RuleSimpleCondition = {
field: 'cm:name',
comparator: 'equals',
parameter: ''
};
export const mimeTypeMock: RuleSimpleCondition = {
field: 'mimetype',
comparator: 'equals',
parameter: ''
};
export const categoryMock: RuleSimpleCondition = {
field: 'category',
comparator: 'equals',
parameter: ''
};
export const simpleConditionUnknownFieldMock: RuleSimpleCondition = {
field: 'unknown-field',
comparator: 'equals',
parameter: ''
};
const emptyCompositeConditionMock: RuleCompositeCondition = {
inverted: false,
booleanMode: 'and',
compositeConditions: [],
simpleConditions: []
};
const compositeConditionWithOneConditionMock: RuleCompositeCondition = {
...emptyCompositeConditionMock,
simpleConditions: [{ ...simpleConditionMock }]
};
export const compositeConditionWithOneGroupMock: RuleCompositeCondition = {
...emptyCompositeConditionMock,
compositeConditions: [{ ...compositeConditionWithOneConditionMock }]
};
export const compositeConditionWithNestedGroupsMock: RuleCompositeCondition = {
...emptyCompositeConditionMock,
compositeConditions: [
{
...emptyCompositeConditionMock,
compositeConditions: [{ ...compositeConditionWithOneConditionMock }]
},
{ ...compositeConditionWithOneConditionMock }
]
};
export const compositeConditionWithThreeConditionMock: RuleCompositeCondition = {
inverted: false,
booleanMode: 'and',
compositeConditions: [],
simpleConditions: [{ ...simpleConditionMock }, { ...simpleConditionMock }, { ...simpleConditionMock }]
};
@@ -0,0 +1,71 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { NodeInfo } from '@alfresco/aca-shared/store';
import { Node, NodeEntry } from '@alfresco/js-api';
export const getOwningFolderEntryMock: NodeEntry = {
entry: {
id: 'owning-folder-id',
name: 'owning-folder-name'
}
} as NodeEntry;
export const getOtherFolderEntryMock: NodeEntry = {
entry: {
id: 'other-folder-id',
name: 'other-folder-name'
}
} as NodeEntry;
export const owningFolderIdMock = 'owning-folder-id';
export const otherFolderIdMock = 'other-folder-id';
export const owningFolderMock: NodeInfo = {
id: owningFolderIdMock,
name: 'owning-folder-name'
};
export const otherFolderMock: NodeInfo = {
id: otherFolderIdMock,
name: 'other-folder-name'
};
export const folderToLinkMock: Node = {
id: 'folder-1-id',
name: 'folder-1-name',
nodeType: 'folder',
isFolder: true,
isFile: false,
modifiedAt: new Date(),
modifiedByUser: {
id: 'user-id',
displayName: 'user-name'
},
createdAt: new Date(),
createdByUser: {
id: 'user-id',
displayName: 'user-name'
}
};
@@ -0,0 +1,160 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleSet } from '../model/rule-set.model';
import { folderToLinkMock, otherFolderIdMock, otherFolderMock, owningFolderIdMock, owningFolderMock } from './node.mock';
import { Rule } from '../model/rule.model';
import { inheritedRulesMock, linkedRulesMock, ownedRulesMock, ruleMock } from './rules.mock';
export const getRuleSetsResponseMock = {
list: {
pagination: {
count: 3,
hasMoreItems: false,
totalItems: 3,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
linkedToBy: [],
owningFolder: otherFolderIdMock,
isLinkedTo: false,
id: 'inherited-rule-set'
}
},
{
entry: {
linkedToBy: [],
owningFolder: owningFolderIdMock,
isLinkedTo: false,
id: 'rule-set-no-links'
}
},
{
entry: {
linkedToBy: [owningFolderIdMock],
owningFolder: otherFolderIdMock,
isLinkedTo: true,
id: 'rule-set-with-link'
}
}
]
}
};
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,
owningFolder: owningFolderMock,
linkedToBy: [],
rules: [...rules],
hasMoreRules: true,
loadingRules: false
});
export const ownedRuleSetMock: RuleSet = {
id: 'rule-set-no-links',
isLinkedTo: false,
owningFolder: owningFolderMock,
linkedToBy: [],
rules: ownedRulesMock,
hasMoreRules: false,
loadingRules: false
};
export const ruleSetWithLinkMock: RuleSet = {
id: 'rule-set-with-link',
isLinkedTo: true,
owningFolder: otherFolderMock,
linkedToBy: [owningFolderIdMock],
rules: linkedRulesMock,
hasMoreRules: false,
loadingRules: false
};
export const inheritedRuleSetMock: RuleSet = {
id: 'inherited-rule-set',
isLinkedTo: false,
owningFolder: otherFolderMock,
linkedToBy: [],
rules: inheritedRulesMock,
hasMoreRules: false,
loadingRules: false
};
export const inheritedRuleSetWithEmptyRulesMock: RuleSet = {
id: 'inherited-rule-set',
isLinkedTo: false,
owningFolder: otherFolderMock,
linkedToBy: [],
rules: [],
hasMoreRules: false,
loadingRules: false
};
export const inheritedRuleSetWithOnlyDisabledRulesMock: RuleSet = {
id: 'inherited-rule-set',
isLinkedTo: false,
owningFolder: otherFolderMock,
linkedToBy: [],
rules: [
{ ...ruleMock('rule1'), isEnabled: false },
{ ...ruleMock('rule2'), isEnabled: false }
],
hasMoreRules: false,
loadingRules: false
};
export const ruleSetsMock: RuleSet[] = [inheritedRuleSetMock, ownedRuleSetMock, ruleSetWithLinkMock];
export const ruleSetWithNoRulesToLinkMock: RuleSet = {
id: 'rule-set-to-link-with-no-rules',
isLinkedTo: false,
owningFolder: folderToLinkMock,
linkedToBy: [],
rules: [],
hasMoreRules: false,
loadingRules: false
};
export const ruleSetWithOwnedRulesToLinkMock: RuleSet = {
id: 'rule-set-to-link-with-no-rules',
isLinkedTo: false,
owningFolder: folderToLinkMock,
linkedToBy: [],
rules: ownedRulesMock,
hasMoreRules: false,
loadingRules: false
};
@@ -0,0 +1,176 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Rule, RuleSettings } from '../model/rule.model';
import { RuleGroupingItem } from '../model/rule-grouping-item.model';
export const getRulesResponseMock = {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
isShared: false,
isInheritable: false,
isAsynchronous: false,
name: 'rule1-name',
id: 'rule1-id',
triggers: ['inbound'],
actions: [
{
actionDefinitionId: 'counter',
params: {}
}
],
isEnabled: true
}
},
{
entry: {
isShared: false,
isInheritable: false,
isAsynchronous: false,
name: 'rule2-name',
id: 'rule2-id',
triggers: ['inbound'],
actions: [
{
actionDefinitionId: 'counter',
params: {}
}
],
isEnabled: true
}
}
]
}
};
export const getMoreRulesResponseMock = {
list: {
pagination: {
count: 2,
hasMoreItems: false,
totalItems: 2,
skipCount: 0,
maxItems: 100
},
entries: [
{
entry: {
isShared: false,
isInheritable: false,
isAsynchronous: false,
name: 'rule3-name',
id: 'rule3-id',
triggers: ['inbound'],
actions: [
{
actionDefinitionId: 'counter',
params: {}
}
],
isEnabled: true
}
},
{
entry: {
isShared: false,
isInheritable: false,
isAsynchronous: false,
name: 'rule4-name',
id: 'rule4-id',
triggers: ['inbound'],
actions: [
{
actionDefinitionId: 'counter',
params: {}
}
],
isEnabled: true
}
}
]
}
};
const genericRuleMock: Rule = {
id: '',
name: '',
description: '',
isEnabled: true,
isInheritable: false,
isAsynchronous: false,
errorScript: '',
isShared: false,
triggers: ['inbound'],
conditions: {
inverted: false,
booleanMode: 'and',
simpleConditions: [],
compositeConditions: []
},
actions: [
{
actionDefinitionId: 'counter',
params: {}
}
]
};
export const ruleMock = (unique: string): Rule => ({
...genericRuleMock,
id: `${unique}-id`,
name: `${unique}-name`
});
export const rulesMock: Rule[] = [ruleMock('rule1'), ruleMock('rule2')];
export const moreRulesMock: Rule[] = [ruleMock('rule3'), ruleMock('rule4')];
export const manyRulesMock: Rule[] = [ruleMock('rule1'), ruleMock('rule2'), ruleMock('rule3'), ruleMock('rule4'), ruleMock('rule5')];
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')
}
];
export const ruleSettingsMock: RuleSettings = {
value: false,
key: '-parameter-'
};
@@ -0,0 +1,33 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export interface ActionParameterConstraint {
name: string;
constraints: ConstraintValue[];
}
export interface ConstraintValue {
value: string;
label: string;
}
@@ -0,0 +1,52 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export interface RuleAction {
actionDefinitionId: string;
params?: { [key: string]: unknown };
}
export const isRuleAction = (obj): obj is RuleAction =>
typeof obj === 'object' && typeof obj.actionDefinitionId === 'string' && (obj.params === undefined || typeof obj.params === 'object');
export const isRuleActions = (obj): obj is RuleAction[] =>
typeof obj === 'object' && obj instanceof Array && obj.reduce((acc, curr) => acc && isRuleAction(curr), true);
export interface ActionDefinitionTransformed {
id: string;
name: string;
description: string;
title: string;
applicableTypes: string[];
trackStatus: boolean;
parameterDefinitions: ActionParameterDefinitionTransformed[];
}
export interface ActionParameterDefinitionTransformed {
name: string;
type: string;
multiValued: boolean;
mandatory: boolean;
displayLabel: string;
parameterConstraintName?: string;
}
@@ -0,0 +1,32 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleSimpleCondition } from './rule-simple-condition.model';
export interface RuleCompositeCondition {
inverted: boolean;
booleanMode: 'and' | 'or';
compositeConditions: RuleCompositeCondition[];
simpleConditions: RuleSimpleCondition[];
}
@@ -0,0 +1,39 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. 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';
};
@@ -0,0 +1,36 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Rule } from './rule.model';
import { NodeInfo } from '@alfresco/aca-shared/store';
export interface RuleSet {
id: string;
isLinkedTo: boolean;
owningFolder: NodeInfo;
linkedToBy: string[];
rules: Rule[];
hasMoreRules: boolean;
loadingRules: boolean;
}
@@ -0,0 +1,29 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export interface RuleSimpleCondition {
field: string;
comparator: string;
parameter: string;
}
@@ -0,0 +1,64 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleCompositeCondition } from './rule-composite-condition.model';
import { RuleAction } from './rule-action.model';
export type RuleTrigger = 'inbound' | 'update' | 'outbound';
export interface Rule {
id: string;
name: string;
description: string;
isEnabled: boolean;
isInheritable: boolean;
isAsynchronous: boolean;
errorScript: string;
isShared: boolean;
triggers: RuleTrigger[];
conditions: RuleCompositeCondition;
actions: RuleAction[];
}
export interface RuleOptions {
isEnabled: boolean;
isInheritable: boolean;
isAsynchronous: boolean;
errorScript: string;
}
export interface RuleForForm {
id: string;
name: string;
description: string;
triggers: RuleTrigger[];
conditions: RuleCompositeCondition;
actions: RuleAction[];
options: RuleOptions;
}
export interface RuleSettings {
value: boolean;
key?: string;
}
@@ -0,0 +1,26 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export * from './folder-rules.module';
export { ManageRulesSmartComponent } from './manage-rules/manage-rules.smart-component';
@@ -0,0 +1,33 @@
<div class="aca-rule-action-list__item " *ngFor="let control of formControls">
<aca-rule-action
[actionDefinitions]="actionDefinitions"
[parameterConstraints]="parameterConstraints"
[readOnly]="readOnly"
[formControl]="control"
[nodeId]="nodeId">
</aca-rule-action>
<button
mat-icon-button
data-automation-id="rule-action-list-action-menu"
*ngIf="!readOnly" [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu>
<button
mat-menu-item
data-automation-id="rule-action-list-remove-action-button"
[title]="'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_BUTTONS.REMOVE' | translate"
[disabled]="formControls.length <= 1"
(click)="removeAction(control)">
<mat-icon>delete</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_BUTTONS.REMOVE' | translate }}</span>
</button>
</mat-menu>
</div>
<button mat-button data-automation-id="rule-action-list-add-action-button" (click)="addAction()" *ngIf="!readOnly">
<mat-icon>add</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_BUTTONS.ADD_ACTION' | translate }}</span>
</button>
@@ -0,0 +1,15 @@
.aca-rule-action-list {
&__item {
padding: 4px 8px;
border-radius: 8px;
display: flex;
& > .aca-rule-action {
flex: 1;
}
&:nth-child(2n) {
background-color: hsl(0deg, 0%, 95%);
}
}
}
@@ -0,0 +1,103 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { RuleActionListUiComponent } from './rule-action-list.ui-component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { RuleActionUiComponent } from './rule-action.ui-component';
describe('RuleActionListUiComponent', () => {
let fixture: ComponentFixture<RuleActionListUiComponent>;
let component: RuleActionListUiComponent;
const getByDataAutomationId = (dataAutomationId: string, index = 0): DebugElement =>
fixture.debugElement.queryAll(By.css(`[data-automation-id="${dataAutomationId}"]`))[index];
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
});
fixture = TestBed.createComponent(RuleActionListUiComponent);
component = fixture.componentInstance;
component.writeValue([]);
fixture.detectChanges();
});
it('should default to 1 empty action when an empty array of actions is written', () => {
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent));
expect(acaRuleActions.length).toBe(1);
});
it('should add a new action when the "add action" button is clicked', () => {
const addActionButton = getByDataAutomationId('rule-action-list-add-action-button').nativeElement as HTMLButtonElement;
addActionButton.click();
fixture.detectChanges();
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent));
expect(acaRuleActions.length).toBe(2);
});
it('should disable the remove button if there is only one action', () => {
const menuButton = getByDataAutomationId('rule-action-list-action-menu', 0).nativeElement as HTMLButtonElement;
menuButton.click();
fixture.detectChanges();
const removeActionButton = getByDataAutomationId('rule-action-list-remove-action-button').nativeElement as HTMLButtonElement;
expect(removeActionButton.disabled).toBeTrue();
});
it('should enable the remove button if there is more than one action', () => {
const addActionButton = getByDataAutomationId('rule-action-list-add-action-button').nativeElement as HTMLButtonElement;
addActionButton.click();
fixture.detectChanges();
const menuButton = getByDataAutomationId('rule-action-list-action-menu', 0).nativeElement as HTMLButtonElement;
menuButton.click();
fixture.detectChanges();
const removeActionButton = getByDataAutomationId('rule-action-list-remove-action-button').nativeElement as HTMLButtonElement;
expect(removeActionButton.disabled).toBeFalse();
});
it('should remove an action when the remove action button is clicked', () => {
const addActionButton = getByDataAutomationId('rule-action-list-add-action-button').nativeElement as HTMLButtonElement;
addActionButton.click();
fixture.detectChanges();
const menuButton = getByDataAutomationId('rule-action-list-action-menu', 0).nativeElement as HTMLButtonElement;
menuButton.click();
fixture.detectChanges();
const removeActionButton = getByDataAutomationId('rule-action-list-remove-action-button').nativeElement as HTMLButtonElement;
removeActionButton.click();
fixture.detectChanges();
const acaRuleActions = fixture.debugElement.queryAll(By.directive(RuleActionUiComponent));
expect(acaRuleActions.length).toBe(1);
});
});
@@ -0,0 +1,108 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, FormArray, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { ActionDefinitionTransformed, RuleAction } from '../../model/rule-action.model';
import { Subscription } from 'rxjs';
import { ruleActionValidator } from '../validators/rule-actions.validator';
import { ActionParameterConstraint } from '../../model/action-parameter-constraint.model';
@Component({
selector: 'aca-rule-action-list',
templateUrl: './rule-action-list.ui-component.html',
styleUrls: ['./rule-action-list.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-action-list' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleActionListUiComponent)
}
]
})
export class RuleActionListUiComponent implements ControlValueAccessor, OnDestroy {
@Input()
actionDefinitions: ActionDefinitionTransformed[] = [];
@Input()
readOnly = false;
@Input()
parameterConstraints: ActionParameterConstraint[] = [];
@Input()
nodeId = '';
formArray = new FormArray([]);
private formArraySubscription: Subscription;
formControls: FormControl[] = [];
onChange: (actions: RuleAction[]) => void = () => undefined;
onTouch: () => void = () => undefined;
writeValue(actions: RuleAction[]) {
if (actions.length === 0) {
actions = [
{
actionDefinitionId: null,
params: {}
}
];
}
this.formArray = new FormArray(actions.map((action: RuleAction) => new FormControl(action)));
this.formControls = this.formArray.controls as FormControl[];
this.formArraySubscription?.unsubscribe();
this.formArraySubscription = this.formArray.valueChanges.subscribe((value: any) => {
this.onChange(value);
this.onTouch();
});
}
registerOnChange(fn: (actions: RuleAction[]) => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouch = fn;
}
addAction() {
const newAction: RuleAction = {
actionDefinitionId: null,
params: {}
};
this.formArray.push(new FormControl(newAction, [Validators.required, ruleActionValidator(this.actionDefinitions)]));
this.formControls = this.formArray.controls as FormControl[];
}
ngOnDestroy() {
this.formArraySubscription?.unsubscribe();
}
removeAction(control: FormControl) {
const index = this.formArray.value.indexOf(control.value);
this.formArray.removeAt(index);
this.formControls = this.formArray.controls as FormControl[];
}
}
@@ -0,0 +1,23 @@
<form class="aca-rule-action__form" [formGroup]="form">
<mat-form-field>
<mat-select
formControlName="actionDefinitionId"
data-automation-id="rule-action-select"
[placeholder]="'ACA_FOLDER_RULES.RULE_DETAILS.ACTION_SELECT_PLACEHOLDER' | translate">
<mat-option
*ngFor="let actionDefinition of actionDefinitions"
[value]="actionDefinition.id">
{{ actionDefinition.title }}
</mat-option>
</mat-select>
</mat-form-field>
<adf-card-view
data-automation-id="rule-action-card-view"
[properties]="cardViewItems"
[ngClass]="{ 'aca-rule-action-full-width': isFullWidth }"
[editable]="!readOnly">
</adf-card-view>
</form>
@@ -0,0 +1,15 @@
.aca-rule-action {
&__form {
display: flex;
flex-direction: row;
gap: 20px;
adf-card-view .adf-property-list .adf-property adf-card-view-item-dispatcher adf-card-view-textitem .mat-form-field .mat-form-field-infix {
width: 280px;
}
}
&-full-width {
width: 100%;
}
}
@@ -0,0 +1,107 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CardViewBoolItemModel, CardViewComponent, CardViewSelectItemModel, CardViewTextItemModel, CoreTestingModule } from '@alfresco/adf-core';
import { RuleActionUiComponent } from './rule-action.ui-component';
import { actionLinkToCategoryTransformedMock, actionsTransformedListMock } from '../../mock/actions.mock';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { dummyConstraints } from '../../mock/action-parameter-constraints.mock';
describe('RuleActionUiComponent', () => {
let fixture: ComponentFixture<RuleActionUiComponent>;
let component: RuleActionUiComponent;
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const changeMatSelectValue = (dataAutomationId: string, value: string) => {
const matSelect = getByDataAutomationId(dataAutomationId).nativeElement;
matSelect.click();
fixture.detectChanges();
const matOption = fixture.debugElement.query(By.css(`.mat-option[ng-reflect-value="${value}"]`)).nativeElement;
matOption.click();
fixture.detectChanges();
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
});
fixture = TestBed.createComponent(RuleActionUiComponent);
component = fixture.componentInstance;
});
it('should populate the dropdown selector with the action definitions', () => {
component.actionDefinitions = actionsTransformedListMock;
fixture.detectChanges();
const matSelect = getByDataAutomationId('rule-action-select').nativeElement;
matSelect.click();
fixture.detectChanges();
const matOptions = fixture.debugElement.queryAll(By.css(`mat-option`));
expect(matOptions.length).toBe(2);
expect(matOptions[0].nativeElement.innerText).toBe('Action 1 title');
expect(matOptions[1].nativeElement.innerText).toBe('mock-action-2-definition');
});
it('should populate the card view with parameters when an action is selected', () => {
component.actionDefinitions = actionsTransformedListMock;
component.parameterConstraints = dummyConstraints;
fixture.detectChanges();
const cardView = getByDataAutomationId('rule-action-card-view').componentInstance as CardViewComponent;
expect(cardView.properties.length).toBe(0);
changeMatSelectValue('rule-action-select', 'mock-action-1-definition');
expect(cardView.properties.length).toBe(5);
expect(cardView.properties[0]).toBeInstanceOf(CardViewTextItemModel);
expect(cardView.properties[1]).toBeInstanceOf(CardViewBoolItemModel);
expect(cardView.properties[2]).toBeInstanceOf(CardViewSelectItemModel);
expect(cardView.properties[3]).toBeInstanceOf(CardViewTextItemModel);
expect(cardView.properties[4]).toBeInstanceOf(CardViewSelectItemModel);
changeMatSelectValue('rule-action-select', 'mock-action-2-definition');
expect(cardView.properties.length).toBe(0);
});
it('should create category-value action parameter as a text box rather than node picker', () => {
component.actionDefinitions = [actionLinkToCategoryTransformedMock];
component.parameterConstraints = dummyConstraints;
fixture.detectChanges();
const cardView = getByDataAutomationId('rule-action-card-view').componentInstance as CardViewComponent;
expect(cardView.properties.length).toBe(0);
changeMatSelectValue('rule-action-select', 'mock-action-3-definition');
expect(cardView.properties[0].icon).toBeFalsy();
expect(cardView.properties[0].value).toBeFalsy();
expect(cardView.properties[0]).toBeInstanceOf(CardViewTextItemModel);
});
});
@@ -0,0 +1,299 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { ActionDefinitionTransformed, RuleAction } from '../../model/rule-action.model';
import { CardViewItem } from '@alfresco/adf-core/lib/card-view/interfaces/card-view-item.interface';
import {
CardViewBoolItemModel,
CardViewSelectItemModel,
CardViewSelectItemOption,
CardViewTextItemModel,
CardViewUpdateService,
UpdateNotification
} from '@alfresco/adf-core';
import { ActionParameterDefinition, Node } from '@alfresco/js-api';
import { of, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ActionParameterConstraint, ConstraintValue } from '../../model/action-parameter-constraint.model';
import { ContentNodeSelectorComponent, ContentNodeSelectorComponentData, NodeAction } from '@alfresco/adf-content-services';
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'aca-rule-action',
templateUrl: './rule-action.ui-component.html',
styleUrls: ['./rule-action.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-action' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleActionUiComponent)
},
CardViewUpdateService
]
})
export class RuleActionUiComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
@Input()
nodeId = '';
@Input()
actionDefinitions: ActionDefinitionTransformed[];
@Input()
readOnly = false;
private _parameterConstraints = [];
@Input()
get parameterConstraints(): ActionParameterConstraint[] {
return this._parameterConstraints;
}
set parameterConstraints(value) {
this._parameterConstraints = value.map((obj) => ({ ...obj, constraints: this.parseConstraintsToSelectOptions(obj.constraints) }));
}
isFullWidth = false;
form = new FormGroup({
actionDefinitionId: new FormControl('', Validators.required)
});
cardViewItems: CardViewItem[] = [];
parameters: { [key: string]: unknown } = {};
private onDestroy$ = new Subject<boolean>();
get selectedActionDefinitionId(): string {
return this.form.get('actionDefinitionId').value;
}
get selectedActionDefinition(): ActionDefinitionTransformed {
return this.actionDefinitions.find((actionDefinition: ActionDefinitionTransformed) => actionDefinition.id === this.selectedActionDefinitionId);
}
get cardViewStyle() {
return this.isFullWidth ? { width: '100%' } : {};
}
onChange: (action: RuleAction) => void = () => undefined;
onTouch: () => void = () => undefined;
constructor(private cardViewUpdateService: CardViewUpdateService, private dialog: MatDialog, private translate: TranslateService) {}
writeValue(action: RuleAction) {
this.form.setValue({
actionDefinitionId: action.actionDefinitionId
});
this.parameters = {
...this.parameters,
...action.params
};
this.setCardViewProperties();
}
registerOnChange(fn: (action: RuleAction) => void) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouch = fn;
}
ngOnInit() {
this.actionDefinitions = this.actionDefinitions.sort((firstActionDefinition, secondActionDefinition) =>
firstActionDefinition.title.localeCompare(secondActionDefinition.title)
);
this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.setDefaultParameters();
this.setCardViewProperties();
this.onChange({
actionDefinitionId: this.selectedActionDefinitionId,
params: this.parameters
});
this.onTouch();
});
this.cardViewUpdateService.itemUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe((updateNotification: UpdateNotification) => {
this.parameters = {
...this.parameters,
...updateNotification.changed
};
this.onChange({
actionDefinitionId: this.selectedActionDefinitionId,
params: this.parameters
});
this.onTouch();
});
}
ngOnChanges(changes: SimpleChanges): void {
const readOnly = changes['readOnly']?.currentValue;
if (readOnly !== undefined && readOnly !== null) {
this.readOnly = readOnly;
this.setDisabledState(readOnly);
}
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
setCardViewProperties() {
this.cardViewItems = (this.selectedActionDefinition?.parameterDefinitions ?? []).map((paramDef) => {
this.isFullWidth = false;
const constraintsForDropdownBox = this._parameterConstraints.find((obj) => obj.name === paramDef.name);
const cardViewPropertiesModel = {
label: paramDef.displayLabel + (paramDef.mandatory ? ' *' : ''),
key: paramDef.name,
editable: true,
...(paramDef.mandatory
? {
validators: [
{
message: 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.REQUIRED',
isValid: (value: unknown) => !!value
}
]
}
: {})
};
switch (paramDef.type) {
case 'd:boolean':
return new CardViewBoolItemModel({
...cardViewPropertiesModel,
value: this.parameters[paramDef.name] ?? false
});
case 'd:noderef':
if (!constraintsForDropdownBox && !this.readOnly && paramDef.name !== 'category-value') {
return new CardViewTextItemModel({
...cardViewPropertiesModel,
icon: 'folder',
default: '',
clickable: true,
clickCallBack: this.openSelectorDialog.bind(this, paramDef.name),
value: this.parameters[paramDef.name]
});
}
// falls through
default:
if (constraintsForDropdownBox && !this.readOnly) {
this.isFullWidth = true;
return new CardViewSelectItemModel({
...cardViewPropertiesModel,
value: (this.parameters[paramDef.name] as string) ?? '',
options$: of(constraintsForDropdownBox.constraints)
});
}
return new CardViewTextItemModel({
...cardViewPropertiesModel,
value: this.parameters[paramDef.name] ?? ''
});
}
});
}
private openSelectorDialog(paramDefName) {
const data: ContentNodeSelectorComponentData = {
title: this.translate.instant('ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.CHOOSE_FOLDER'),
actionName: NodeAction.CHOOSE,
currentFolderId: this.nodeId,
select: new Subject<Node[]>()
};
this.dialog.open(ContentNodeSelectorComponent, {
data,
panelClass: 'adf-content-node-selector-dialog',
width: '630px'
});
data.select.subscribe(
(selections: Node[]) => {
if (selections[0].id) {
this.writeValue({
actionDefinitionId: this.selectedActionDefinitionId,
params: {
...this.parameters,
[paramDefName]: selections[0].id
}
});
this.onChange({
actionDefinitionId: this.selectedActionDefinitionId,
params: this.parameters
});
this.onTouch();
}
},
(error) => {
console.error(error);
},
() => {
this.dialog.closeAll();
}
);
}
setDefaultParameters() {
this.parameters = {};
(this.selectedActionDefinition?.parameterDefinitions ?? []).forEach((paramDef: ActionParameterDefinition) => {
switch (paramDef.type) {
case 'd:boolean':
this.parameters[paramDef.name] = false;
break;
default:
this.parameters[paramDef.name] = '';
}
});
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.readOnly = true;
this.form.disable();
} else {
this.readOnly = false;
this.form.enable();
}
}
private parseConstraintsToSelectOptions(constraints: ConstraintValue[]): CardViewSelectItemOption<unknown>[] {
return constraints
.sort((a, b) => {
if (!a.label && b.label) {
return 1;
}
if (!b.label && a.label) {
return -1;
}
return a.label?.localeCompare(b.label) ?? -1;
})
.map((constraint) => ({
key: constraint.value,
label: constraint.label ? `${constraint.label} [${constraint.value}]` : constraint.value
}));
}
}
@@ -0,0 +1,68 @@
<form class="aca-rule-composite-condition__form" [formGroup]="form">
<div *ngIf="hasNoConditions" class="aca-rule-composite-condition__form__no-conditions" data-automation-id="no-conditions">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.' + (childCondition ? 'NO_CONDITIONS_IN_GROUP' : 'NO_CONDITIONS') | translate }}
</div>
<div
class="aca-rule-composite-condition__form__row"
*ngFor="let control of conditionFormControls; let i = index">
<mat-form-field *ngIf="i === 0">
<mat-select
[formControl]="invertedControl"
[disabled]="readOnly">
<mat-option [value]="false">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.IF' | translate }}</mat-option>
<mat-option [value]="true">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.NOT_IF' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="i > 0">
<mat-select
[disabled]="!isOrImplemented || i > 1 || readOnly"
[formControl]="booleanModeControl">
<mat-option value="and">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.AND' | translate }}</mat-option>
<mat-option value="or">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LOGIC_OPERATORS.OR' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<aca-rule-composite-condition
*ngIf="!isFormControlSimpleCondition(control)"
[secondaryBackground]="!secondaryBackground"
[childCondition]="true"
[formControl]="control"
[readOnly]="readOnly">
</aca-rule-composite-condition>
<aca-rule-simple-condition
*ngIf="isFormControlSimpleCondition(control)"
[formControl]="control"
[readOnly]="readOnly">
</aca-rule-simple-condition>
<button mat-icon-button [matMenuTriggerFor]="menu" *ngIf="!readOnly" data-automation-id="condition-actions-button">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button
mat-menu-item
[title]="'ACA_FOLDER_RULES.RULE_DETAILS.CONDITION_BUTTONS.REMOVE' | translate"
(click)="removeCondition(control)">
<mat-icon>delete</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.CONDITION_BUTTONS.REMOVE' | translate }}</span>
</button>
</mat-menu>
</div>
<div class="aca-rule-composite-condition__form__actions" *ngIf="!readOnly" data-automation-id="add-actions">
<button mat-button (click)="addSimpleCondition()" data-automation-id="add-condition-button">
<mat-icon>add</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.CONDITION_BUTTONS.ADD_CONDITION' | translate }}</span>
</button>
<button mat-button (click)="addCompositeCondition()" data-automation-id="add-group-button">
<mat-icon>add</mat-icon>
<span>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.CONDITION_BUTTONS.ADD_GROUP' | translate }}</span>
</button>
</div>
</form>
@@ -0,0 +1,38 @@
.aca-rule-composite-condition {
display: block;
border-radius: 8px;
&.childCompositeCondition {
padding: 8px 16px;
background-color: hsl(0deg, 0%, 100%);
&.secondaryBackground {
background-color: hsl(0deg, 0%, 95%);
}
}
&__form {
display: flex;
flex-direction: column;
gap: 8px;
&__no-conditions {
color: var(--theme-disabled-text-color);
margin: 0.5em 0;
}
&__row {
display: flex;
gap: 8px;
& > :nth-child(1) {
width: 5em;
font-size: inherit;
}
& > :nth-child(2) {
flex: 1;
}
}
}
}
@@ -0,0 +1,142 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleCompositeConditionUiComponent } from './rule-composite-condition.ui-component';
import { CoreTestingModule } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import {
compositeConditionWithNestedGroupsMock,
compositeConditionWithOneGroupMock,
compositeConditionWithThreeConditionMock
} from '../../mock/conditions.mock';
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
describe('RuleCompositeConditionUiComponent', () => {
let fixture: ComponentFixture<RuleCompositeConditionUiComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [RuleCompositeConditionUiComponent, RuleSimpleConditionUiComponent]
});
fixture = TestBed.createComponent(RuleCompositeConditionUiComponent);
});
describe('No conditions', () => {
let noConditionsElement: DebugElement;
beforeEach(() => {
fixture.detectChanges();
noConditionsElement = fixture.debugElement.query(By.css(`[data-automation-id="no-conditions"]`));
});
it('should default to no conditions', () => {
const rowElements = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition__form__row`));
expect(rowElements.length).toBe(0);
});
it('should show a message if there are no conditions', () => {
fixture.componentInstance.childCondition = false;
fixture.detectChanges();
expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS');
});
it('should show a different message if the component is not a root condition group', () => {
fixture.componentInstance.childCondition = true;
fixture.detectChanges();
expect((noConditionsElement.nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.NO_CONDITIONS_IN_GROUP');
});
});
describe('Read only mode', () => {
it('should hide the add buttons in read only mode', () => {
fixture.componentInstance.setDisabledState(true);
fixture.detectChanges();
const actionsElement = fixture.debugElement.query(By.css(`[data-automation-id="add-actions"]`));
expect(actionsElement).toBeNull();
});
it('should hide the more actions button on the right side of the condition', () => {
fixture.componentInstance.writeValue(compositeConditionWithOneGroupMock);
fixture.componentInstance.setDisabledState(true);
fixture.detectChanges();
const actionsButtonElements = fixture.debugElement.queryAll(By.css(`[data-automation-id="condition-actions-button"]`));
expect(actionsButtonElements.length).toBe(0);
});
});
it('should have as many simple condition components as defined in the simpleConditions array', () => {
fixture.componentInstance.writeValue(compositeConditionWithThreeConditionMock);
fixture.detectChanges();
const simpleConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-simple-condition`));
expect(simpleConditionComponents.length).toBe(3);
});
it('should have as many composite condition components as defined in the compositeConditions array, including nested', () => {
fixture.componentInstance.writeValue(compositeConditionWithNestedGroupsMock);
fixture.detectChanges();
const compositeConditionComponents = fixture.debugElement.queryAll(By.css(`.aca-rule-composite-condition`));
expect(compositeConditionComponents.length).toBe(3);
});
it('should add a simple condition component on click of add condition button', () => {
fixture.detectChanges();
const predicate = By.css(`.aca-rule-simple-condition`);
const simpleConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
expect(simpleConditionComponentsBeforeClick.length).toBe(0);
const addConditionButton = fixture.debugElement.query(By.css(`[data-automation-id="add-condition-button"]`));
(addConditionButton.nativeElement as HTMLButtonElement).click();
fixture.detectChanges();
const simpleConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
expect(simpleConditionComponentsAfterClick.length).toBe(1);
});
it('should add a composite condition component on click of add group button', () => {
fixture.detectChanges();
const predicate = By.css(`.aca-rule-composite-condition`);
const compositeConditionComponentsBeforeClick = fixture.debugElement.queryAll(predicate);
expect(compositeConditionComponentsBeforeClick.length).toBe(0);
const addGroupButton = fixture.debugElement.query(By.css(`[data-automation-id="add-group-button"]`));
(addGroupButton.nativeElement as HTMLButtonElement).click();
fixture.detectChanges();
const compositeConditionComponentsAfterClick = fixture.debugElement.queryAll(predicate);
expect(compositeConditionComponentsAfterClick.length).toBe(1);
});
});
@@ -0,0 +1,154 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, HostBinding, Input, OnChanges, OnDestroy, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
import { ControlValueAccessor, FormArray, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
@Component({
selector: 'aca-rule-composite-condition',
templateUrl: './rule-composite-condition.ui-component.html',
styleUrls: ['./rule-composite-condition.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-composite-condition' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleCompositeConditionUiComponent)
}
]
})
export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnDestroy, OnChanges {
@HostBinding('class.secondaryBackground')
@Input()
secondaryBackground = false;
@HostBinding('class.childCompositeCondition')
@Input()
childCondition = false;
form = new FormGroup({
inverted: new FormControl(),
booleanMode: new FormControl(),
compositeConditions: new FormArray([]),
simpleConditions: new FormArray([])
});
readonly isOrImplemented = false;
@Input()
public readOnly = false;
private formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.onChange(value);
this.onTouch();
});
public invertedControl = this.form.get('inverted') as FormControl;
public booleanModeControl = this.form.get('booleanMode') as FormControl;
get compositeConditionsFormArray(): FormArray {
return this.form.get('compositeConditions') as FormArray;
}
get simpleConditionsFormArray(): FormArray {
return this.form.get('simpleConditions') as FormArray;
}
get conditionFormControls(): FormControl[] {
return [...(this.compositeConditionsFormArray.controls as FormControl[]), ...(this.simpleConditionsFormArray.controls as FormControl[])];
}
get hasNoConditions(): boolean {
return this.conditionFormControls.length === 0;
}
onChange: (condition: RuleCompositeCondition) => void = () => undefined;
onTouch: () => void = () => undefined;
writeValue(value: RuleCompositeCondition) {
this.form.get('inverted').setValue(value.inverted);
this.form.get('booleanMode').setValue(value.booleanMode);
this.form.setControl('compositeConditions', new FormArray(value.compositeConditions.map((condition) => new FormControl(condition))));
this.form.setControl('simpleConditions', new FormArray(value.simpleConditions.map((condition) => new FormControl(condition))));
}
registerOnChange(fn: () => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.readOnly = true;
this.form.disable();
} else {
this.readOnly = false;
this.form.enable();
}
}
isFormControlSimpleCondition(control: FormControl): boolean {
// eslint-disable-next-line no-prototype-builtins
return control.value.hasOwnProperty('field');
}
removeCondition(control: FormControl) {
const formArray = this.isFormControlSimpleCondition(control) ? this.simpleConditionsFormArray : this.compositeConditionsFormArray;
const index = (formArray.value as FormControl[]).indexOf(control.value);
formArray.removeAt(index);
}
addSimpleCondition() {
const newCondition: RuleSimpleCondition = {
field: 'cm:name',
comparator: 'equals',
parameter: ''
};
this.simpleConditionsFormArray.push(new FormControl(newCondition));
}
addCompositeCondition() {
const newCondition: RuleCompositeCondition = {
inverted: false,
booleanMode: 'and',
compositeConditions: [],
simpleConditions: []
};
this.compositeConditionsFormArray.push(new FormControl(newCondition));
}
ngOnDestroy() {
this.formSubscription.unsubscribe();
}
ngOnChanges(changes: SimpleChanges): void {
const readOnly = changes['readOnly'].currentValue;
if (readOnly !== undefined && readOnly !== null) {
this.readOnly = readOnly;
this.setDisabledState(readOnly);
}
}
}
@@ -0,0 +1,94 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export interface RuleConditionComparator {
name: string;
labels: {
[key: string]: string;
};
}
export const ruleConditionComparators: RuleConditionComparator[] = [
{
name: 'equals',
labels: {
string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.EQUALS',
number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.EQUALS',
date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON',
special: ''
}
},
{
name: 'contains',
labels: {
string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.CONTAINS'
}
},
{
name: 'begins',
labels: {
string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.STARTS_WITH'
}
},
{
name: 'ends',
labels: {
string: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ENDS_WITH'
}
},
{
name: 'greater_than',
labels: {
number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.GREATER_THAN',
date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.AFTER'
}
},
{
name: 'less_than',
labels: {
number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.LESS_THAN',
date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.BEFORE'
}
},
{
name: 'greater_than_equal',
labels: {
number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.GREATER_THAN_OR_EQUAL',
date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON_OR_AFTER'
}
},
{
name: 'less_than_equal',
labels: {
number: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.LESS_THAN_OR_EQUAL',
date: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.ON_OR_BEFORE'
}
},
{
name: 'instance_of',
labels: {
type: 'ACA_FOLDER_RULES.RULE_DETAILS.COMPARATORS.INSTANCE_OF'
}
}
];
@@ -0,0 +1,71 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export type RuleConditionFieldType = 'string' | 'number' | 'date' | 'type' | 'special' | 'mimeType';
export interface RuleConditionField {
name: string;
label: string;
type: RuleConditionFieldType;
}
export const comparatorHiddenForConditionFieldType: string[] = ['special', 'mimeType'];
export const ruleConditionFields: RuleConditionField[] = [
{
name: 'cm:name',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.NAME',
type: 'string'
},
{
name: 'size',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.SIZE',
type: 'number'
},
{
name: 'mimetype',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.MIMETYPE',
type: 'mimeType'
},
{
name: 'encoding',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.ENCODING',
type: 'special'
},
{
name: 'category',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_CATEGORY',
type: 'special'
},
{
name: 'tag',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_TAG',
type: 'special'
},
{
name: 'aspect',
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_ASPECT',
type: 'special'
}
];
@@ -0,0 +1,28 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
export interface MimeType {
label: string;
value: string;
}
@@ -0,0 +1,35 @@
<form class="aca-rule-simple-condition__form" [formGroup]="form">
<mat-form-field class="aca-rule-simple-condition__form__field-input">
<mat-select formControlName="field" data-automation-id="field-select"
(selectionChange)="onChangeField()">
<mat-option *ngIf="!isSelectedFieldKnown" [value]="selectedField.name" data-automation-id="unknown-field-option">
{{ selectedField.label }}
</mat-option>
<mat-option *ngFor="let field of fields" [value]="field.name">
{{ field.label | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="aca-rule-simple-condition__form__comparator-input" [class]="{ hidden: isComparatorHidden }" data-automation-id="comparator-form-field">
<mat-select formControlName="comparator" data-automation-id="comparator-select">
<mat-option
*ngFor="let comparator of selectedFieldComparators"
[value]="comparator.name">
{{ comparator.labels[this.selectedField?.type || 'equals'] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="aca-rule-simple-condition__form__parameter-input">
<mat-select formControlName="parameter" data-automation-id="simple-condition-value-select" *ngIf="selectedField?.type === 'mimeType'; else valueInput">
<mat-option *ngFor="let mimeType of mimeTypes"
[value]="mimeType.value">
{{ mimeType.label }}
</mat-option>
</mat-select>
<ng-template #valueInput>
<input matInput placeholder="{{ 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.VALUE' | translate }}" type="text" formControlName="parameter" data-automation-id="value-input">
</ng-template>
</mat-form-field>
</form>
@@ -0,0 +1,18 @@
.aca-rule-simple-condition {
&__form {
display: flex;
flex-direction: row;
gap: 8px;
mat-form-field {
flex: 2;
font-size: inherit;
}
&__comparator-input {
&.hidden {
display: none;
}
}
}
}
@@ -0,0 +1,174 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
import { CoreTestingModule } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { categoryMock, mimeTypeMock, simpleConditionUnknownFieldMock } from '../../mock/conditions.mock';
import { MimeType } from './rule-mime-types';
describe('RuleSimpleConditionUiComponent', () => {
let fixture: ComponentFixture<RuleSimpleConditionUiComponent>;
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const changeMatSelectValue = (dataAutomationId: string, value: string) => {
const matSelect = getByDataAutomationId(dataAutomationId).nativeElement;
matSelect.click();
fixture.detectChanges();
const matOption = fixture.debugElement.query(By.css(`.mat-option[ng-reflect-value="${value}"]`)).nativeElement;
matOption.click();
fixture.detectChanges();
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [RuleSimpleConditionUiComponent]
});
fixture = TestBed.createComponent(RuleSimpleConditionUiComponent);
});
it('should default the field to the name, the comparator to equals and the value empty', () => {
fixture.detectChanges();
expect(getByDataAutomationId('field-select').componentInstance.value).toBe('cm:name');
expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals');
expect(getByDataAutomationId('value-input').nativeElement.value).toBe('');
});
it('should hide the comparator select box if the type of the field is special', () => {
fixture.detectChanges();
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement;
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
changeMatSelectValue('field-select', 'category');
expect(fixture.componentInstance.isComparatorHidden).toBeTruthy();
expect(getComputedStyle(comparatorFormField).display).toBe('none');
});
it('should hide the comparator select box if the type of the field is mimeType', () => {
fixture.detectChanges();
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement;
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
changeMatSelectValue('field-select', 'mimetype');
expect(fixture.componentInstance.isComparatorHidden).toBeTruthy();
expect(getComputedStyle(comparatorFormField).display).toBe('none');
});
it('should set the comparator to equals if the field is set to a type with different comparators', () => {
const onChangeFieldSpy = spyOn(fixture.componentInstance, 'onChangeField').and.callThrough();
fixture.detectChanges();
changeMatSelectValue('comparator-select', 'contains');
expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('contains');
changeMatSelectValue('field-select', 'mimetype');
expect(onChangeFieldSpy).toHaveBeenCalledTimes(1);
expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals');
changeMatSelectValue('field-select', 'size');
expect(onChangeFieldSpy).toHaveBeenCalledTimes(2);
expect(getByDataAutomationId('comparator-select').componentInstance.value).toBe('equals');
});
it('should display an additional option for a currently selected unknown field', () => {
fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock);
fixture.detectChanges();
expect(getByDataAutomationId('field-select').componentInstance.value).toBe(simpleConditionUnknownFieldMock.field);
const matSelect = getByDataAutomationId('field-select').nativeElement;
matSelect.click();
fixture.detectChanges();
const unknownOptionMatOption = getByDataAutomationId('unknown-field-option');
expect(unknownOptionMatOption).not.toBeNull();
expect((unknownOptionMatOption.nativeElement as HTMLElement).innerText.trim()).toBe(simpleConditionUnknownFieldMock.field);
});
it('should remove the option for the unknown field as soon as another option is selected', () => {
fixture.componentInstance.writeValue(simpleConditionUnknownFieldMock);
fixture.detectChanges();
changeMatSelectValue('field-select', 'cm:name');
const matSelect = getByDataAutomationId('field-select').nativeElement;
matSelect.click();
fixture.detectChanges();
const unknownOptionMatOption = getByDataAutomationId('unknown-field-option');
expect(unknownOptionMatOption).toBeNull();
});
it('should provide select option when mimeType is selected and value filled', () => {
const mockMimeTypes: MimeType[] = [
{
value: 'video/3gpp',
label: '3G Video'
},
{
value: 'video/3gpp2',
label: '3G2 Video'
},
{
value: 'application/vnd.alfresco.ai.features.v1+json',
label: 'AI-Features'
},
{
value: 'application/vnd.alfresco.ai.labels.v1+json',
label: 'AI-Labels'
}
];
fixture.componentInstance.writeValue(mimeTypeMock);
fixture.componentInstance.mimeTypes = mockMimeTypes;
fixture.componentInstance.onChangeField();
fixture.detectChanges();
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy();
expect(fixture.componentInstance.form.get('parameter').value).toEqual(mockMimeTypes[0].value);
});
it('should set value to empty when any condition is selected after mimeType', () => {
fixture.componentInstance.writeValue(mimeTypeMock);
fixture.detectChanges();
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy();
fixture.componentInstance.writeValue(categoryMock);
fixture.detectChanges();
expect(getByDataAutomationId('value-input').nativeElement.value).toBe('');
});
});
@@ -0,0 +1,145 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
import { comparatorHiddenForConditionFieldType, RuleConditionField, ruleConditionFields } from './rule-condition-fields';
import { RuleConditionComparator, ruleConditionComparators } from './rule-condition-comparators';
import { AppConfigService } from '@alfresco/adf-core';
import { MimeType } from './rule-mime-types';
@Component({
selector: 'aca-rule-simple-condition',
templateUrl: './rule-simple-condition.ui-component.html',
styleUrls: ['./rule-simple-condition.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-simple-condition' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleSimpleConditionUiComponent)
}
]
})
export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnDestroy {
readonly fields = ruleConditionFields;
form = new FormGroup({
field: new FormControl('cm:name'),
comparator: new FormControl('equals'),
parameter: new FormControl()
});
mimeTypes: MimeType[] = [];
private _readOnly = false;
@Input()
get readOnly(): boolean {
return this._readOnly;
}
set readOnly(isReadOnly: boolean) {
this.setDisabledState(isReadOnly);
}
constructor(private config: AppConfigService) {
this.mimeTypes = this.config.get<Array<MimeType>>('mimeTypes');
}
private formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.onChange(value);
this.onTouch();
});
get isSelectedFieldKnown(): boolean {
const selectedFieldName = this.form.get('field').value;
return this.fields.findIndex((field: RuleConditionField) => selectedFieldName === field.name) > -1;
}
get selectedField(): RuleConditionField {
const selectedFieldName = this.form.get('field').value;
if (!this.isSelectedFieldKnown) {
return {
name: selectedFieldName,
label: selectedFieldName,
type: 'special'
};
}
return this.fields.find((field) => field.name === selectedFieldName);
}
get selectedFieldComparators(): RuleConditionComparator[] {
return ruleConditionComparators.filter((comparator) => Object.keys(comparator.labels).includes(this.selectedField.type));
}
get isComparatorHidden(): boolean {
return comparatorHiddenForConditionFieldType.includes(this.selectedField?.type);
}
get comparatorControl(): AbstractControl {
return this.form.get('comparator');
}
private get parameterControl(): AbstractControl<string> {
return this.form.get('parameter');
}
onChange: (condition: RuleSimpleCondition) => void = () => undefined;
onTouch: () => void = () => undefined;
writeValue(value: RuleSimpleCondition) {
this.form.setValue(value);
}
registerOnChange(fn: () => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this._readOnly = true;
this.form.disable();
} else {
this._readOnly = false;
this.form.enable();
}
}
onChangeField() {
if (!this.selectedFieldComparators.find((comparator) => comparator.name === this.comparatorControl.value)) {
this.comparatorControl.setValue('equals');
}
if (!this.parameterControl.value && this.selectedField?.type === 'mimeType') {
this.parameterControl.setValue(this.mimeTypes[0]?.value);
} else if (this.parameterControl.value) {
this.parameterControl.setValue('');
}
}
ngOnDestroy() {
this.formSubscription.unsubscribe();
}
}
@@ -0,0 +1,129 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditRuleDialogOptions, EditRuleDialogUiComponent } from './edit-rule-dialog.ui-component';
import { By } from '@angular/platform-browser';
import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CoreTestingModule } from '@alfresco/adf-core';
import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component';
import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component';
import { RuleActionListUiComponent } from './actions/rule-action-list.ui-component';
import { RuleActionUiComponent } from './actions/rule-action.ui-component';
import { RuleOptionsUiComponent } from './options/rule-options.ui-component';
import { of, timer } from 'rxjs';
describe('EditRuleDialogSmartComponent', () => {
let fixture: ComponentFixture<EditRuleDialogUiComponent>;
const dialogRef = {
close: jasmine.createSpy('close'),
open: jasmine.createSpy('open')
};
const setupBeforeEach = (dialogOptions: EditRuleDialogOptions = { actionDefinitions$: of([]), parameterConstraints$: of([]) }) => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [
EditRuleDialogUiComponent,
RuleCompositeConditionUiComponent,
RuleDetailsUiComponent,
RuleTriggersUiComponent,
RuleActionListUiComponent,
RuleActionUiComponent,
RuleOptionsUiComponent
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: dialogOptions }
]
});
fixture = TestBed.createComponent(EditRuleDialogUiComponent);
fixture.detectChanges();
};
describe('No dialog options passed / indifferent', () => {
beforeEach(() => {
setupBeforeEach();
});
it('should activate the submit button only when a valid state is received', async () => {
const submitButton = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement;
const ruleDetails = fixture.debugElement.query(By.directive(RuleDetailsUiComponent)).componentInstance as RuleDetailsUiComponent;
ruleDetails.formValidationChanged.emit(true);
fixture.detectChanges();
// timer needed to wait for the next tick to avoid ExpressionChangedAfterItHasBeenCheckedError
await timer(1).toPromise();
fixture.detectChanges();
expect(submitButton.disabled).toBeFalsy();
ruleDetails.formValidationChanged.emit(false);
fixture.detectChanges();
await timer(1).toPromise();
fixture.detectChanges();
expect(submitButton.disabled).toBeTruthy();
});
it('should show a "create" label in the title', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-title"]')).nativeElement as HTMLDivElement;
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE_TITLE');
});
it('should show a "create" label in the submit button', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement;
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CREATE');
});
});
describe('With dialog options passed', () => {
const dialogOptions: EditRuleDialogOptions = {
model: {
id: 'rule-id'
},
actionDefinitions$: of([]),
parameterConstraints$: of([])
};
beforeEach(() => {
setupBeforeEach(dialogOptions);
});
it('should show an "update" label in the title', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-title"]')).nativeElement as HTMLDivElement;
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE_TITLE');
});
it('should show an "update" label in the submit button', () => {
const titleElement = fixture.debugElement.query(By.css('[data-automation-id="edit-rule-dialog-submit"]')).nativeElement as HTMLButtonElement;
expect(titleElement.innerText.trim()).toBe('ACA_FOLDER_RULES.EDIT_RULE_DIALOG.UPDATE');
});
});
});
@@ -0,0 +1,24 @@
<div mat-dialog-title class="aca-edit-rule-dialog__header">
<div class="aca-edit-rule-dialog__header__title" data-automation-id="edit-rule-dialog-title">
{{ title | translate }}
</div>
<button mat-icon-button mat-dialog-close class="aca-edit-rule-dialog__header__close" tabindex="-1">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-dialog-content class="aca-edit-rule-dialog__content">
<aca-rule-details
[actionDefinitions]="actionDefinitions$ | async"
[parameterConstraints]="parameterConstraints$ | async"
[value]="model"
[nodeId]="nodeId"
(formValueChanged)="formValue = $event"
(formValidationChanged)="onFormValidChange($event)">
</aca-rule-details>
</mat-dialog-content>
<mat-dialog-actions align="end" class="aca-edit-rule-dialog__footer">
<button mat-flat-button mat-dialog-close>{{ 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.CANCEL' | translate }}</button>
<button mat-flat-button color="primary" [disabled]="!formValid" data-automation-id="edit-rule-dialog-submit" (click)="onSubmit()">{{ submitLabel | translate }}</button>
</mat-dialog-actions>
@@ -0,0 +1,49 @@
.aca-edit-rule-dialog-container {
--edit-rule-dialog-padding: 8px 20px;
.mat-dialog-container {
padding: 0;
}
}
.aca-edit-rule-dialog {
&__header {
display: flex;
align-items: center;
margin: 0;
padding: var(--edit-rule-dialog-padding);
box-sizing: border-box;
border-bottom: 1px solid var(--theme-border-color);
&__title {
font-size: 16px;
font-weight: bold;
flex-grow: 1;
}
&__close {
& mat-icon {
font-size: 18px;
}
}
}
&__content {
margin: 0;
padding: 0;
&__spinner {
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
}
}
&__footer {
margin: 0;
padding: var(--edit-rule-dialog-padding);
box-sizing: border-box;
border-top: 1px solid var(--theme-border-color);
}
}
@@ -0,0 +1,78 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, EventEmitter, Inject, Output, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Rule } from '../model/rule.model';
import { Observable } from 'rxjs';
import { ActionDefinitionTransformed } from '../model/rule-action.model';
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
export interface EditRuleDialogOptions {
model?: Partial<Rule>;
nodeId?: string;
actionDefinitions$?: Observable<ActionDefinitionTransformed[]>;
parameterConstraints$?: Observable<ActionParameterConstraint[]>;
}
@Component({
selector: 'aca-edit-rule-dialog',
templateUrl: './edit-rule-dialog.ui-component.html',
styleUrls: ['./edit-rule-dialog.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-edit-rule-dialog' }
})
export class EditRuleDialogUiComponent {
formValid = false;
model: Partial<Rule>;
nodeId = '';
actionDefinitions$;
parameterConstraints$;
formValue: Partial<Rule>;
@Output() submitted = new EventEmitter<Partial<Rule>>();
title = 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE_TITLE' : 'CREATE_TITLE');
submitLabel = 'ACA_FOLDER_RULES.EDIT_RULE_DIALOG.' + (this.isUpdateMode ? 'UPDATE' : 'CREATE');
constructor(@Inject(MAT_DIALOG_DATA) public data: EditRuleDialogOptions) {
this.model = this.data?.model || {};
this.nodeId = this.data?.nodeId;
this.actionDefinitions$ = this.data?.actionDefinitions$;
this.parameterConstraints$ = this.data?.parameterConstraints$;
}
get isUpdateMode(): boolean {
return !!this.data?.model?.id;
}
onSubmit() {
this.submitted.emit(this.formValue);
}
onFormValidChange(isValid: boolean) {
// setTimeout needed to avoid ExpressionChangedAfterItHasBeenCheckedError
setTimeout(() => {
this.formValid = isValid;
}, 0);
}
}
@@ -0,0 +1,46 @@
<ng-container [formGroup]="form">
<div class="aca-rule-options__option" *ngIf="!readOnly || isAsynchronousChecked">
<mat-checkbox
formControlName="isAsynchronous"
(change)="toggleErrorScriptDropdown($event)"
data-automation-id="rule-option-checkbox-asynchronous">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.IS_ASYNCHRONOUS' | translate }}
</mat-checkbox>
<mat-form-field
data-automation-id="rule-option-form-field-errorScript"
floatLabel="always"
[ngClass]="{ 'hide-error-script-dropdown': hideErrorScriptDropdown }">
<mat-select
formControlName="errorScript"
placeholder="{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.ERROR_SCRIPT' | translate}}"
data-automation-id="rule-option-select-errorScript">
<mat-option value="">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.NO_SCRIPT' | translate }}</mat-option>
<mat-option *ngFor="let option of errorScriptOptions"
[value]="option.value">
{{ option.label }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="aca-rule-options__option" *ngIf="!readOnly || isInheritableChecked">
<mat-checkbox
formControlName="isInheritable"
data-automation-id="rule-option-checkbox-inheritable">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.IS_INHERITABLE' | translate }}
</mat-checkbox>
</div>
<div class="aca-rule-options__option" *ngIf="!readOnly">
<mat-checkbox
formControlName="isDisabled"
data-automation-id="rule-option-checkbox-disabled">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.DISABLE_RULE' | translate }}
</mat-checkbox>
</div>
</ng-container>
@@ -0,0 +1,18 @@
.aca-rule-options {
display: flex;
flex-direction: row;
gap: 24px;
&__option {
display: flex;
flex-direction: column;
}
&.read-only .mat-checkbox-inner-container {
display: none;
}
.hide-error-script-dropdown {
opacity: 0;
}
}
@@ -0,0 +1,153 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RuleOptionsUiComponent } from './rule-options.ui-component';
import { CoreTestingModule } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { errorScriptConstraintMock } from '../../mock/actions.mock';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
describe('RuleOptionsUiComponent', () => {
let fixture: ComponentFixture<RuleOptionsUiComponent>;
let component: RuleOptionsUiComponent;
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const toggleMatCheckbox = (dataAutomationId: string) => {
((getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).children[0] as HTMLElement).click();
};
beforeEach(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [FormsModule, ReactiveFormsModule, CoreTestingModule],
declarations: [RuleOptionsUiComponent],
providers: [{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { floatLabel: 'never' } }]
});
fixture = TestBed.createComponent(RuleOptionsUiComponent);
component = fixture.componentInstance;
component.writeValue({
isEnabled: true,
isInheritable: false,
isAsynchronous: false,
errorScript: ''
});
});
it('should be able to write to the component', () => {
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeTruthy();
component.writeValue({
isEnabled: false,
isInheritable: true,
isAsynchronous: true,
errorScript: ''
});
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-disabled').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy();
});
it('should enable selector when async checkbox is truthy', () => {
fixture.detectChanges();
toggleMatCheckbox('rule-option-checkbox-asynchronous');
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-option-select-errorScript').componentInstance.disabled).toBeFalsy();
});
it('should hide disabled checkbox and unselected checkboxes in read-only mode', () => {
component.readOnly = true;
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeFalsy();
expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript')).toBeFalsy();
component.writeValue({
isEnabled: false,
isInheritable: true,
isAsynchronous: true,
errorScript: ''
});
fixture.detectChanges();
expect(getByDataAutomationId('rule-option-checkbox-asynchronous')).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-inheritable')).toBeTruthy();
expect(getByDataAutomationId('rule-option-checkbox-enabled')).toBeFalsy();
expect(getByDataAutomationId('rule-option-select-errorScript')).toBeTruthy();
});
it('should populate the error script dropdown with scripts', () => {
component.writeValue({
isEnabled: true,
isInheritable: false,
isAsynchronous: true,
errorScript: ''
});
component.errorScriptConstraint = errorScriptConstraintMock;
fixture.detectChanges();
(getByDataAutomationId('rule-option-select-errorScript').nativeElement as HTMLElement).click();
fixture.detectChanges();
const matOptions = fixture.debugElement.queryAll(By.css(`.mat-option`));
expect(matOptions.length).toBe(3);
expect((matOptions[0].nativeElement as HTMLElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_DETAILS.OPTIONS.NO_SCRIPT');
expect((matOptions[1].nativeElement as HTMLElement).innerText.trim()).toBe('Script 1');
expect((matOptions[2].nativeElement as HTMLElement).innerText.trim()).toBe('Script 2');
});
it('should always show a label for the error script dropdown even when MAT_FORM_FIELD_DEFAULT_OPTIONS sets floatLabel to never', () => {
component.writeValue({
isEnabled: true,
isInheritable: false,
isAsynchronous: true,
errorScript: ''
});
component.errorScriptConstraint = errorScriptConstraintMock;
fixture.detectChanges();
const matFormField = fixture.debugElement.query(By.css(`[data-automation-id="rule-option-form-field-errorScript"] .mat-form-field-label`));
fixture.detectChanges();
expect(matFormField).not.toBeNull();
expect(matFormField.componentInstance['floatLabel']).toBe('always');
});
});
@@ -0,0 +1,133 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, HostBinding, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { RuleOptions } from '../../model/rule.model';
import { ActionParameterConstraint, ConstraintValue } from '../../model/action-parameter-constraint.model';
@Component({
selector: 'aca-rule-options',
templateUrl: 'rule-options.ui-component.html',
styleUrls: ['rule-options.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-options' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleOptionsUiComponent)
}
]
})
export class RuleOptionsUiComponent implements ControlValueAccessor, OnInit, OnDestroy {
form = new FormGroup({
isDisabled: new FormControl(),
isInheritable: new FormControl(),
isAsynchronous: new FormControl(),
errorScript: new FormControl()
});
formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.isAsynchronousChecked = value.isAsynchronous;
this.isInheritableChecked = value.isInheritable;
this.onChange({
isEnabled: !value.isDisabled,
isInheritable: value.isInheritable,
isAsynchronous: value.isAsynchronous,
errorScript: value.errorScript ?? ''
});
this.onTouch();
});
hideErrorScriptDropdown = true;
@Input()
errorScriptConstraint: ActionParameterConstraint;
@HostBinding('class.read-only')
readOnly = false;
onChange: (options: RuleOptions) => void = () => undefined;
onTouch: () => void = () => undefined;
isAsynchronousChecked = false;
isInheritableChecked = false;
errorScriptOptions: ConstraintValue[] = [];
writeValue(options: RuleOptions) {
const isAsynchronousFormControl = this.form.get('isAsynchronous');
const errorScriptFormControl = this.form.get('errorScript');
this.form.get('isDisabled').setValue(!options.isEnabled);
this.form.get('isInheritable').setValue(options.isInheritable);
this.form.get('isAsynchronous').setValue(options.isAsynchronous);
errorScriptFormControl.setValue(options.errorScript ?? '');
if (isAsynchronousFormControl.value) {
this.hideErrorScriptDropdown = false;
errorScriptFormControl.enable();
} else {
this.hideErrorScriptDropdown = true;
errorScriptFormControl.disable();
}
}
registerOnChange(fn: () => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.form.disable();
this.readOnly = true;
} else {
this.form.enable();
this.readOnly = false;
}
}
ngOnInit(): void {
this.errorScriptOptions = this.errorScriptConstraint?.constraints ?? [];
}
ngOnDestroy() {
this.formSubscription.unsubscribe();
}
toggleErrorScriptDropdown(value: MatCheckboxChange) {
const formControl: AbstractControl = this.form.get('errorScript');
if (value.checked) {
this.hideErrorScriptDropdown = false;
formControl.enable();
} else {
this.hideErrorScriptDropdown = true;
formControl.disable();
}
}
}
@@ -0,0 +1,64 @@
<form class="aca-rule-details__form" [ngClass]="{ 'read-only': readOnly }" [formGroup]="form">
<ng-container *ngIf="!preview">
<div class="aca-rule-details__form__row aca-rule-details__form__name">
<label for="rule-details-name-input">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.NAME' | translate }}</label>
<div>
<mat-form-field floatLabel='never'>
<input
id="rule-details-name-input"
matInput type="text" formControlName="name" data-automation-id="rule-details-name-input"
[placeholder]="'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.NAME' | translate">
<mat-error>{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.REQUIRED' | translate }}</mat-error>
</mat-form-field>
</div>
</div>
<div class="aca-rule-details__form__row aca-rule-details__form__description">
<label for="rule-details-description-textarea">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.DESCRIPTION' | translate }}</label>
<div>
<mat-form-field floatLabel='never'>
<textarea
id="rule-details-description-textarea"
matInput formControlName="description" data-automation-id="rule-details-description-textarea"
[placeholder]="descriptionPlaceHolder | translate">
</textarea>
</mat-form-field>
</div>
</div>
</ng-container>
<div class="aca-rule-details__form__row aca-rule-details__form__triggers">
<div class="label">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.WHEN' | translate }}</div>
<div>
<aca-rule-triggers formControlName="triggers" data-automation-id="rule-details-triggers-component"></aca-rule-triggers>
<mat-error class="rule-details-error" *ngIf="triggers.hasError('required')">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.INSUFFICIENT_TRIGGERS_SELECTED' | translate }}</mat-error>
</div>
</div>
<div class="aca-rule-details__form__conditions">
<aca-rule-composite-condition formControlName="conditions"></aca-rule-composite-condition>
<mat-error class="rule-details-error" *ngIf="conditions.hasError('ruleCompositeConditionInvalid')">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.ERROR.RULE_COMPOSITE_CONDITION_INVALID' | translate }}</mat-error>
</div>
<div class="aca-rule-details__form__row aca-rule-details__form__actions">
<div class="label">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.PERFORM_ACTIONS' | translate }}</div>
<aca-rule-action-list
formControlName="actions"
[actionDefinitions]="actionDefinitions"
[parameterConstraints]="parameterConstraints"
[readOnly]="readOnly"
[nodeId]="nodeId">
</aca-rule-action-list>
</div>
<div class="aca-rule-details__form__row" *ngIf="showOptionsSection">
<div class="label">{{ 'ACA_FOLDER_RULES.RULE_DETAILS.LABEL.OPTIONS' | translate }}</div>
<aca-rule-options
formControlName="options"
data-automation-id="rule-details-options-component"
[errorScriptConstraint]="errorScriptConstraint">
</aca-rule-options>
</div>
</form>
@@ -0,0 +1,92 @@
.aca-rule-details {
&__form {
position: relative;
& > div {
padding: 8px 20px;
width: 100%;
box-sizing: border-box;
overflow-x: auto;
&.aca-rule-details__form__name {
padding-bottom: 0;
}
&.aca-rule-details__form__description {
padding-top: 0;
border: none;
align-items: flex-start;
}
}
&__row {
display: flex;
align-items: baseline;
gap: 8px;
& > label,
& > .label {
font-weight: bold;
width: 20%;
min-width: 100px;
max-width: 150px;
padding: 0.75em 0;
}
& > div {
flex-grow: 1;
mat-form-field {
width: 100%;
max-width: 400px;
font-size: inherit;
.mat-form-field-infix {
border: none;
}
}
}
}
&:not(:nth-child(1)) {
border-top: 1px solid var(--theme-border-color);
}
&__conditions {
width: 100%;
& > .rule-details-error {
margin-left: 16px;
color: inherit;
}
}
textarea {
min-height: 4em;
}
.rule-details-error {
font-size: 75%;
height: 1em;
}
&__actions {
.aca-rule-action-list {
flex: 1;
}
}
&.read-only,
.mat-form-field-disabled {
.mat-form-field-underline,
.mat-select-arrow-wrapper {
display: none;
}
*:disabled,
.mat-select-disabled .mat-select-value {
color: inherit;
}
}
}
}
@@ -0,0 +1,154 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { RuleDetailsUiComponent } from './rule-details.ui-component';
import { Rule } from '../model/rule.model';
import { By } from '@angular/platform-browser';
import { RuleCompositeConditionUiComponent } from './conditions/rule-composite-condition.ui-component';
import { RuleTriggersUiComponent } from './triggers/rule-triggers.ui-component';
import { RuleOptionsUiComponent } from './options/rule-options.ui-component';
import { RuleActionListUiComponent } from './actions/rule-action-list.ui-component';
import { RuleActionUiComponent } from './actions/rule-action.ui-component';
describe('RuleDetailsUiComponent', () => {
let fixture: ComponentFixture<RuleDetailsUiComponent>;
let component: RuleDetailsUiComponent;
const testValue: Partial<Rule> = {
id: 'rule-id',
name: 'Rule name',
description: 'This is the description of the rule',
triggers: ['update', 'outbound'],
isAsynchronous: true,
isInheritable: true,
isEnabled: true,
errorScript: ''
};
const getHtmlElement = <T>(dataAutomationId: string) =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`))?.nativeElement as T;
const getComponentInstance = <T>(dataAutomationId: string) =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`))?.componentInstance as T;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [
RuleCompositeConditionUiComponent,
RuleDetailsUiComponent,
RuleTriggersUiComponent,
RuleOptionsUiComponent,
RuleActionListUiComponent,
RuleActionUiComponent
]
});
fixture = TestBed.createComponent(RuleDetailsUiComponent);
component = fixture.componentInstance;
});
it('should fill the form out with initial values', () => {
component.value = testValue;
fixture.detectChanges();
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionsComponent = getComponentInstance<RuleOptionsUiComponent>('rule-details-options-component');
expect(nameInput.value).toBe(testValue.name);
expect(descriptionTextarea.value).toBe(testValue.description);
expect(ruleTriggersComponent.value).toEqual(testValue.triggers);
expect(ruleOptionsComponent.form.value).toEqual({
isDisabled: !testValue.isEnabled,
isInheritable: testValue.isInheritable,
isAsynchronous: testValue.isAsynchronous,
errorScript: testValue.errorScript
});
});
it('should modify the form if the value input property is modified', () => {
fixture.detectChanges();
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
expect(nameInput.value).toBe('');
expect(descriptionTextarea.value).toBe('');
expect(ruleTriggersComponent.value).toEqual(['inbound']);
component.value = testValue;
fixture.detectChanges();
expect(nameInput.value).toBe(testValue.name);
expect(descriptionTextarea.value).toBe(testValue.description);
expect(ruleTriggersComponent.value).toEqual(testValue.triggers);
});
it('should be editable if not read-only', () => {
component.value = testValue;
component.readOnly = false;
fixture.detectChanges();
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionsComponent = getComponentInstance<RuleOptionsUiComponent>('rule-details-options-component');
expect(nameInput.disabled).toBeFalsy();
expect(descriptionTextarea.disabled).toBeFalsy();
expect(ruleTriggersComponent.readOnly).toBeFalsy();
expect(ruleOptionsComponent.readOnly).toBeFalsy();
});
it('should not be editable if read-only', () => {
component.value = testValue;
component.readOnly = true;
fixture.detectChanges();
const nameInput = getHtmlElement<HTMLInputElement>('rule-details-name-input');
const descriptionTextarea = getHtmlElement<HTMLTextAreaElement>('rule-details-description-textarea');
const ruleTriggersComponent = getComponentInstance<RuleTriggersUiComponent>('rule-details-triggers-component');
const ruleOptionsComponent = getComponentInstance<RuleOptionsUiComponent>('rule-details-options-component');
expect(nameInput.disabled).toBeTruthy();
expect(descriptionTextarea.disabled).toBeTruthy();
expect(ruleTriggersComponent.readOnly).toBeTruthy();
expect(ruleOptionsComponent.readOnly).toBeTruthy();
});
it('should hide the options section entirely in read-only mode if it has no selected options', () => {
component.value = {
...testValue,
isInheritable: false,
isAsynchronous: false
};
component.readOnly = true;
fixture.detectChanges();
expect(getComponentInstance<RuleOptionsUiComponent>('rule-details-options-component')).toBeFalsy();
});
});
@@ -0,0 +1,177 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { Rule, RuleForForm } from '../model/rule.model';
import { ruleCompositeConditionValidator } from './validators/rule-composite-condition.validator';
import { FolderRulesService } from '../services/folder-rules.service';
import { ActionDefinitionTransformed } from '../model/rule-action.model';
import { ruleActionsValidator } from './validators/rule-actions.validator';
import { ActionParameterConstraint } from '../model/action-parameter-constraint.model';
@Component({
selector: 'aca-rule-details',
templateUrl: './rule-details.ui-component.html',
styleUrls: ['./rule-details.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-details' }
})
export class RuleDetailsUiComponent implements OnInit, OnDestroy {
@Input()
readOnly: boolean;
descriptionPlaceHolder: string;
private _initialValue: RuleForForm = FolderRulesService.emptyRuleForForm;
@Input()
get value(): Partial<Rule> {
let value = this.form ? this.form.value : this._initialValue;
if (value.options) {
value = {
...value,
...(value.options ?? FolderRulesService.emptyRuleOptions)
};
delete value.options;
}
return value;
}
set value(newValue: Partial<Rule>) {
const newValueForForm: RuleForForm = {
id: newValue.id || FolderRulesService.emptyRule.id,
name: newValue.name || FolderRulesService.emptyRule.name,
description: newValue.description || FolderRulesService.emptyRule.description,
triggers: newValue.triggers || FolderRulesService.emptyRule.triggers,
conditions: newValue.conditions || FolderRulesService.emptyRule.conditions,
actions: newValue.actions || FolderRulesService.emptyRule.actions,
options: {
isEnabled: typeof newValue.isInheritable == 'boolean' ? newValue.isEnabled : FolderRulesService.emptyRule.isEnabled,
isInheritable: newValue.isInheritable || FolderRulesService.emptyRule.isInheritable,
isAsynchronous: newValue.isAsynchronous || FolderRulesService.emptyRule.isAsynchronous,
errorScript: newValue.errorScript || FolderRulesService.emptyRule.errorScript
}
};
if (this.form) {
this.form.setValue(newValueForForm);
} else {
this._initialValue = newValueForForm;
}
}
@Input()
preview: boolean;
@Input()
actionDefinitions: ActionDefinitionTransformed[] = [];
@Input()
parameterConstraints: ActionParameterConstraint[] = [];
@Input()
nodeId = '';
@Output()
formValidationChanged = new EventEmitter<boolean>();
@Output()
formValueChanged = new EventEmitter<Partial<Rule>>();
private onDestroy$ = new Subject();
form: UntypedFormGroup;
errorScriptConstraint: ActionParameterConstraint;
get name(): UntypedFormControl {
return this.form.get('name') as UntypedFormControl;
}
get description(): UntypedFormControl {
return this.form.get('description') as UntypedFormControl;
}
get triggers(): UntypedFormControl {
return this.form.get('triggers') as UntypedFormControl;
}
get conditions(): UntypedFormControl {
return this.form.get('conditions') as UntypedFormControl;
}
get showOptionsSection(): boolean {
return !this.readOnly || this.value.isAsynchronous || this.value.isInheritable;
}
ngOnInit() {
this.form = new UntypedFormGroup({
id: new UntypedFormControl(this.value.id),
name: new UntypedFormControl(this.value.name || '', Validators.required),
description: new UntypedFormControl(this.value.description || ''),
triggers: new UntypedFormControl(this.value.triggers || ['inbound'], Validators.required),
conditions: new UntypedFormControl(
this.value.conditions || {
inverted: false,
booleanMode: 'and',
compositeConditions: [],
simpleConditions: []
},
ruleCompositeConditionValidator()
),
actions: new UntypedFormControl(this.value.actions, [Validators.required, ruleActionsValidator(this.actionDefinitions)]),
options: new UntypedFormControl({
isEnabled: this.value.isEnabled,
isInheritable: this.value.isInheritable,
isAsynchronous: this.value.isAsynchronous,
errorScript: this.value.errorScript
})
});
this.form.statusChanges
.pipe(
map(() => this.form.valid),
distinctUntilChanged(),
takeUntil(this.onDestroy$)
)
.subscribe((value: boolean) => {
this.formValidationChanged.emit(value);
});
this.formValidationChanged.emit(this.form.valid);
this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.formValueChanged.emit(this.value);
});
if (this.readOnly) {
this.form.disable();
} else {
this.form.enable();
}
this.descriptionPlaceHolder = this.readOnly
? 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.NO_DESCRIPTION'
: 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.DESCRIPTION';
this.errorScriptConstraint = this.parameterConstraints.find(
(parameterConstraint: ActionParameterConstraint) => parameterConstraint.name === 'script-ref'
);
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
}
@@ -0,0 +1,18 @@
<div *ngFor="let trigger of triggerOptions">
<ng-container *ngIf="readOnly; else checkbox">
<div
*ngIf="selectedTriggers[trigger]"
[attr.data-automation-id]="'rule-trigger-value-' + trigger | lowercase">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.TRIGGERS.' + trigger | uppercase | translate }}
</div>
</ng-container>
<ng-template #checkbox>
<mat-checkbox
[attr.data-automation-id]="'rule-trigger-checkbox-' + trigger | lowercase"
[checked]="selectedTriggers[trigger]"
(change)="onTriggerChange(trigger, $event.checked)">
{{ 'ACA_FOLDER_RULES.RULE_DETAILS.TRIGGERS.' + trigger | uppercase | translate }}
</mat-checkbox>
</ng-template>
</div>
@@ -0,0 +1,105 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleTriggersUiComponent } from './rule-triggers.ui-component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('RuleTriggerUiComponent', () => {
let fixture: ComponentFixture<RuleTriggersUiComponent>;
let component: RuleTriggersUiComponent;
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
const toggleMatCheckbox = (dataAutomationId: string) => {
((getByDataAutomationId(dataAutomationId).nativeElement as HTMLElement).children[0] as HTMLElement).click();
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [RuleTriggersUiComponent]
});
fixture = TestBed.createComponent(RuleTriggersUiComponent);
component = fixture.componentInstance;
});
it('should default to only the inbound checkbox', () => {
fixture.detectChanges();
expect(component.value).toEqual(['inbound']);
expect(getByDataAutomationId('rule-trigger-checkbox-inbound').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-trigger-checkbox-update').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound').componentInstance.checked).toBeFalsy();
});
it('should change the checked boxes when the value is written to', () => {
fixture.detectChanges();
component.writeValue(['update', 'outbound']);
fixture.detectChanges();
expect(component.value).toEqual(['update', 'outbound']);
expect(getByDataAutomationId('rule-trigger-checkbox-inbound').componentInstance.checked).toBeFalsy();
expect(getByDataAutomationId('rule-trigger-checkbox-update').componentInstance.checked).toBeTruthy();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound').componentInstance.checked).toBeTruthy();
});
it('should update the value when a checkbox is checked', () => {
const onChangeSpy = spyOn(component, 'onChange');
fixture.detectChanges();
toggleMatCheckbox('rule-trigger-checkbox-update');
fixture.detectChanges();
expect(component.value).toEqual(['inbound', 'update']);
expect(onChangeSpy).toHaveBeenCalledWith(['inbound', 'update']);
});
it('should update the value when a checkbox is unchecked', () => {
const onChangeSpy = spyOn(component, 'onChange');
fixture.detectChanges();
toggleMatCheckbox('rule-trigger-checkbox-inbound');
fixture.detectChanges();
expect(component.value).toEqual([]);
expect(onChangeSpy).toHaveBeenCalledWith([]);
});
it('should only show the correct triggers in read only mode', () => {
component.setDisabledState(true);
component.writeValue(['update', 'outbound']);
fixture.detectChanges();
expect(getByDataAutomationId('rule-trigger-checkbox-inbound')).toBeNull();
expect(getByDataAutomationId('rule-trigger-checkbox-update')).toBeNull();
expect(getByDataAutomationId('rule-trigger-checkbox-outbound')).toBeNull();
expect(getByDataAutomationId('rule-trigger-value-inbound')).toBeNull();
expect(getByDataAutomationId('rule-trigger-value-update')).not.toBeNull();
expect(getByDataAutomationId('rule-trigger-value-outbound')).not.toBeNull();
});
});
@@ -0,0 +1,86 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Component, forwardRef, ViewEncapsulation } from '@angular/core';
import { RuleTrigger } from '../../model/rule.model';
@Component({
selector: 'aca-rule-triggers',
templateUrl: './rule-triggers.ui-component.html',
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-triggers' },
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => RuleTriggersUiComponent)
}
]
})
export class RuleTriggersUiComponent implements ControlValueAccessor {
readonly triggerOptions: RuleTrigger[] = ['inbound', 'update', 'outbound'];
public selectedTriggers: { [key: string]: boolean } = {
inbound: true
};
value: RuleTrigger[] = ['inbound'];
readOnly = false;
onChange: (triggers: RuleTrigger[]) => void = () => undefined;
onTouch: () => void = () => undefined;
writeValue(triggers: RuleTrigger[]) {
this.value = triggers;
this.selectedTriggers = {};
this.value.forEach((trigger) => (this.selectedTriggers[trigger] = true));
}
registerOnChange(fn: () => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean) {
this.readOnly = isDisabled;
}
onTriggerChange(trigger: RuleTrigger, checked: boolean) {
if (checked) {
this.value.push(trigger);
} else {
this.value.splice(
this.value.findIndex((t) => t === trigger),
1
);
}
this.selectedTriggers[trigger] = checked;
this.onTouch();
this.onChange([...this.value]);
}
}
@@ -0,0 +1,68 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { FormControl, ValidatorFn } from '@angular/forms';
import { ruleActionsValidator, ruleActionValidator } from './rule-actions.validator';
import {
actionsTransformedListMock,
incompleteMandatoryParameterMock,
missingMandatoryParameterMock,
nonExistentActionDefinitionIdMock,
validActionMock,
validActionsMock
} from '../../mock/actions.mock';
describe('ruleActionsValidator', () => {
let validatorFn: ValidatorFn;
beforeEach(() => {
validatorFn = ruleActionValidator(actionsTransformedListMock);
});
it('should return null for a valid action', () => {
const control = new FormControl(validActionMock);
expect(validatorFn(control)).toBeNull();
});
it('should return a validation error for an non-existent action definition ID', () => {
const control = new FormControl(nonExistentActionDefinitionIdMock);
expect(validatorFn(control)).toEqual({ ruleActionInvalid: true });
});
it('should return a validation error for an missing mandatory parameter', () => {
const control = new FormControl(missingMandatoryParameterMock);
expect(validatorFn(control)).toEqual({ ruleActionInvalid: true });
});
it('should return a validation error for an incomplete mandatory parameter', () => {
const control = new FormControl(incompleteMandatoryParameterMock);
expect(validatorFn(control)).toEqual({ ruleActionInvalid: true });
});
it('should return null for valid actions', () => {
const multipleActionsValidatorFn = ruleActionsValidator(actionsTransformedListMock);
const control = new FormControl(validActionsMock);
expect(multipleActionsValidatorFn(control)).toBeNull();
});
});
@@ -0,0 +1,60 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import {
ActionDefinitionTransformed,
ActionParameterDefinitionTransformed,
isRuleAction,
isRuleActions,
RuleAction
} from '../../model/rule-action.model';
const isRuleActionValid = (value: unknown, actionDefinitions: ActionDefinitionTransformed[]): boolean => {
const actionDefinition = isRuleAction(value)
? actionDefinitions.find((actionDef: ActionDefinitionTransformed) => value.actionDefinitionId === actionDef.id)
: undefined;
return (
isRuleAction(value) &&
actionDefinition &&
actionDefinition.parameterDefinitions.reduce(
(isValid: boolean, paramDef: ActionParameterDefinitionTransformed) => isValid && (!paramDef.mandatory || !!value.params[paramDef.name]),
true
)
);
};
const isRuleActionsValid = (value: unknown, actionDefinitions: ActionDefinitionTransformed[]): boolean =>
isRuleActions(value) &&
value.reduce((isValid: boolean, currentAction: RuleAction) => isValid && isRuleActionValid(currentAction, actionDefinitions), true);
export const ruleActionValidator =
(actionDefinitions: ActionDefinitionTransformed[]): ValidatorFn =>
(control: AbstractControl): ValidationErrors | null =>
isRuleActionValid(control.value, actionDefinitions) ? null : { ruleActionInvalid: true };
export const ruleActionsValidator =
(actionDefinitions: ActionDefinitionTransformed[]): ValidatorFn =>
(control: AbstractControl): ValidationErrors | null =>
isRuleActionsValid(control.value, actionDefinitions) ? null : { ruleActionsInvalid: true };
@@ -0,0 +1,41 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
const isCompositeConditionValid = (value: RuleCompositeCondition, isRootCondition = true): boolean => {
if (value.compositeConditions.length > 0) {
return value.compositeConditions.reduce(
(arrayValid: boolean, nestedCondition: RuleCompositeCondition) => arrayValid && isCompositeConditionValid(nestedCondition, false),
true
);
}
return !!value.simpleConditions.length || isRootCondition;
};
export const ruleCompositeConditionValidator =
(): ValidatorFn =>
(control: AbstractControl): ValidationErrors | null =>
isCompositeConditionValid(control.value) ? null : { ruleCompositeConditionInvalid: true };
@@ -0,0 +1,48 @@
<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]="item.rule.id === this.selectedRule?.id"
[showEnabledToggle]="showEnabledToggles"
(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>
@@ -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;
}
}
}
@@ -0,0 +1,68 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. 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 '../../folder-rules.module';
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'));
expect(name.nativeElement.textContent).toBe(rulesMock[0].name);
expect(description.nativeElement.textContent).toBe(rulesMock[0].description);
});
});
@@ -0,0 +1,73 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
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-list-grouping',
templateUrl: 'rule-list-grouping.ui-component.html',
styleUrls: ['rule-list-grouping.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-list-grouping' }
})
export class RuleListGroupingUiComponent {
@Input()
items: RuleGroupingItem[] = [];
@Input()
selectedRule: Rule = null;
@Input()
showEnabledToggles = false;
@Output()
selectRule = new EventEmitter<Rule>();
@Output()
ruleEnabledChanged = new EventEmitter<[Rule, boolean]>();
@Output()
loadMoreRules = new EventEmitter<RuleSet>();
@Output()
loadMoreRuleSets = new EventEmitter<void>();
onRuleClicked(rule: Rule): void {
this.selectRule.emit(rule);
}
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();
}
}
@@ -0,0 +1,12 @@
<div class="aca-rule-list-item__header">
<span class="aca-rule-list-item__header__name">{{ rule.name }}</span>
<mat-slide-toggle
*ngIf="showEnabledToggle"
[checked]="rule.isEnabled"
(click)="onToggleClick(!rule.isEnabled, $event)">
</mat-slide-toggle>
</div>
<div class="aca-rule-list-item__description">{{ rule.description }}</div>
@@ -0,0 +1,40 @@
.aca-rule-list-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 20px;
cursor: pointer;
p {
margin: 6px 0 0;
color: rgba(33, 35, 40, 0.7);
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
}
&__header {
display: flex;
justify-content: space-between;
align-items: flex-end;
&__name {
font-size: 1.2em;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
}
&__description {
font-size: 0.8em;
font-style: italic;
overflow: hidden;
text-overflow: ellipsis;
}
&.selected {
background: var(--theme-selected-button-bg-color);
}
}
@@ -0,0 +1,52 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, EventEmitter, HostBinding, Input, Output, ViewEncapsulation } from '@angular/core';
import { Rule } from '../../model/rule.model';
@Component({
selector: 'aca-rule-list-item',
templateUrl: 'rule-list-item.ui-component.html',
styleUrls: ['rule-list-item.ui-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-list-item' }
})
export class RuleListItemUiComponent {
@Input()
rule: Rule;
@Input()
@HostBinding('class.selected')
isSelected: boolean;
@Input()
showEnabledToggle = false;
@Output()
enabledChanged = new EventEmitter<boolean>();
onToggleClick(isEnabled: boolean, event: Event) {
event.stopPropagation();
this.rule.isEnabled = !this.rule.isEnabled;
this.enabledChanged.emit(isEnabled);
}
}
@@ -0,0 +1,90 @@
<div
*ngIf="inheritedRuleSetGroupingItems.length > 0"
class="aca-rule-list__item"
data-automation-id="rule-list-item"
[ngClass]="{ expanded: inheritedRuleSetsExpanded }">
<div
tabindex="0"
class="aca-rule-list__item__header"
matRipple matRippleColor="hsla(0,0%,0%,0.05)"
(click)="inheritedRuleSetsExpanded = !inheritedRuleSetsExpanded"
(keyup.enter)="inheritedRuleSetsExpanded = !inheritedRuleSetsExpanded">
<span class="aca-rule-list__item__header__title">
{{ '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>
<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 || isMainRuleSetLinked"
class="aca-rule-list__item"
data-automation-id="rule-list-item"
[ngClass]="{ expanded: mainRuleSetExpanded }">
<div
tabindex="0"
class="aca-rule-list__item__header"
matRipple matRippleColor="hsla(0,0%,0%,0.05)"
(click)="mainRuleSetExpanded = !mainRuleSetExpanded"
(keyup.enter)="mainRuleSetExpanded = !mainRuleSetExpanded">
<span class="aca-rule-list__item__header__title" data-automation-id="main-rule-set-title">
<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>
</span>
<ng-container *ngIf="!isMainRuleSetOwned">
<mat-icon class="aca-rule-list__item__header__icon" (click)="onRuleSetEditLinkClicked($event)">edit</mat-icon>
<mat-icon class="aca-rule-list__item__header__icon" (click)="onRuleSetUnlinkClicked($event)">link_off</mat-icon>
</ng-container>
<mat-icon class="aca-rule-list__item__header__icon">
{{ mainRuleSetExpanded ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</div>
<ng-container *ngIf="mainRuleSetExpanded">
<aca-rule-list-grouping
*ngIf="mainRuleSetGroupingItems.length > 0; else emptyLinkedRuleSet"
[items]="mainRuleSetGroupingItems"
[selectedRule]="selectedRule"
[showEnabledToggles]="isMainRuleSetOwned"
(selectRule)="onSelectRule($event)"
(ruleEnabledChanged)="onRuleEnabledChanged($event)"
(loadMoreRules)="onLoadMoreRules($event)">
</aca-rule-list-grouping>
<ng-template #emptyLinkedRuleSet>
<div class="aca-rule-list__item__all-linked-rules-are-disabled">
{{ 'ACA_FOLDER_RULES.RULE_LIST.ALL_LINKED_RULES_ARE_DISABLED' | translate }}
<button mat-stroked-button [routerLink]="['/nodes', mainRuleSet.owningFolder.id, 'rules']">
{{ 'ACA_FOLDER_RULES.MANAGE_RULES.TOOLBAR.ACTIONS.SEE_IN_FOLDER' | translate }}
</button>
</div>
</ng-template>
</ng-container>
</div>
@@ -0,0 +1,63 @@
.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;
gap: 4px;
align-items: stretch;
cursor: pointer;
color: var(--theme-text-color);
user-select: none;
font-size: 0.9em;
padding: 0.5em 1em;
& > * {
display: flex;
flex-direction: row;
align-items: center;
}
&__title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex: 1;
.mat-icon {
transform: scale(0.8);
}
}
}
&.expanded {
.aca-rule-list__item__header {
border-bottom: 1px solid var(--theme-border-color);
}
}
&__all-linked-rules-are-disabled {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
color: var(--theme-disabled-text-color);
font-style: italic;
text-align: center;
padding: 0.5em 0;
font-size: 0.9em;
}
}
}
@@ -0,0 +1,69 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleListUiComponent } from './rule-list.ui-component';
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 { owningFolderIdMock } from '../../mock/node.mock';
describe('RuleListUiComponent', () => {
let fixture: ComponentFixture<RuleListUiComponent>;
let component: RuleListUiComponent;
let debugElement: DebugElement;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
declarations: [RuleListUiComponent, RuleListGroupingUiComponent, RuleListItemUiComponent]
});
fixture = TestBed.createComponent(RuleListUiComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
component.folderId = owningFolderIdMock;
component.inheritedRuleSets = ruleSetsMock;
});
it('should show "Rules from current folder" as a title if the main rule set is owned', () => {
component.mainRuleSet = ownedRuleSetMock;
fixture.detectChanges();
const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`));
expect((mainRuleSetTitleElement.nativeElement as HTMLDivElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_LIST.OWNED_RULES');
});
it('should show "Rules from linked folder" as a title if the main rule set is linked', () => {
component.mainRuleSet = ruleSetWithLinkMock;
fixture.detectChanges();
const mainRuleSetTitleElement = debugElement.query(By.css(`[data-automation-id="main-rule-set-title"]`));
expect((mainRuleSetTitleElement.nativeElement as HTMLDivElement).innerText.trim()).toBe('ACA_FOLDER_RULES.RULE_LIST.LINKED_RULES');
});
});
@@ -0,0 +1,139 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
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'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-list' }
})
export class RuleListUiComponent {
@Input()
mainRuleSet: RuleSet = null;
@Input()
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]>();
@Output()
ruleSetEditLinkClicked = new EventEmitter<RuleSet>();
@Output()
ruleSetUnlinkClicked = new EventEmitter<RuleSet>();
inheritedRuleSetsExpanded = true;
mainRuleSetExpanded = true;
get isMainRuleSetOwned(): boolean {
return FolderRuleSetsService.isOwnedRuleSet(this.mainRuleSet, this.folderId);
}
get isMainRuleSetLinked(): boolean {
return FolderRuleSetsService.isLinkedRuleSet(this.mainRuleSet, this.folderId);
}
get mainRuleSetGroupingItems(): RuleGroupingItem[] {
return this.mainRuleSet ? this.getRuleSetGroupingItems(this.mainRuleSet, !this.isMainRuleSetOwned) : [];
}
get inheritedRuleSetGroupingItems(): RuleGroupingItem[] {
const items = this.inheritedRuleSets.reduce((accumulator: RuleGroupingItem[], currentRuleSet: RuleSet) => {
accumulator.push(...this.getRuleSetGroupingItems(currentRuleSet, true));
return accumulator;
}, []);
if (this.ruleSetsLoading || this.hasMoreRuleSets) {
items.push({
type: this.ruleSetsLoading ? 'loading' : 'load-more-rule-sets'
});
}
return items;
}
getRuleSetGroupingItems(ruleSet: RuleSet, filterOutDisabledRules: boolean): RuleGroupingItem[] {
const items: RuleGroupingItem[] = ruleSet.rules
.filter((rule: Rule) => rule.isEnabled || !filterOutDisabledRules)
.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);
}
onRuleEnabledChanged(event: [Rule, boolean]) {
this.ruleEnabledChanged.emit(event);
}
onRuleSetEditLinkClicked(event: Event) {
event.stopPropagation();
this.ruleSetEditLinkClicked.emit(this.mainRuleSet);
}
onRuleSetUnlinkClicked(event: Event) {
event.stopPropagation();
this.ruleSetUnlinkClicked.emit(this.mainRuleSet);
}
}
@@ -0,0 +1,59 @@
<div mat-dialog-title class="aca-rule-set-picker__header">
<div class="aca-rule-set-picker__header__title">
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.TITLE' | translate }}
</div>
<button mat-icon-button mat-dialog-close class="aca-rule-set-picker__header__close" tabindex="-1">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-dialog-content class="aca-rule-set-picker__content">
<adf-content-node-selector-panel
class="aca-rule-set-picker__content__node-selector"
[currentFolderId]="defaultNodeId"
(select)="onNodeSelect($event)"
(folderLoaded)="setFolderLoading(false)"
(navigationChange)="setFolderLoading(true)"
(siteChange)="setFolderLoading(true)">
</adf-content-node-selector-panel>
<div class="aca-rule-set-picker__content__rule-list" [ngClass]="{ justify: rulesLoading$ | async }">
<ng-container *ngIf="rulesLoading$ | async; else rulesLoaded">
<mat-progress-spinner color="primary" mode="indeterminate"></mat-progress-spinner>
</ng-container>
<ng-template #rulesLoaded>
<ng-container *ngIf="hasOwnedRules; else noOwnedRules">
<div class="aca-rule-set-picker__content__rule-list__header">
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.LIST_OF_RULES_TO_LINK' | translate }}
</div>
<aca-rule-list-item
*ngFor="let rule of (mainRuleSet$ | async).rules"
[rule]="rule">
</aca-rule-list-item>
</ng-container>
<ng-template #noOwnedRules>
<adf-empty-content
icon="library_books"
[title]="'ACA_FOLDER_RULES.LINK_RULES_DIALOG.EMPTY_RULES_LIST.TITLE' | translate"
[subtitle]="'ACA_FOLDER_RULES.LINK_RULES_DIALOG.EMPTY_RULES_LIST.SUBTITLE' | translate">
</adf-empty-content>
</ng-template>
</ng-template>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end" class="aca-rule-set-picker__footer">
<button mat-flat-button mat-dialog-close>
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.CANCEL' | translate }}
</button>
<button
mat-flat-button color="primary"
[disabled]="!hasOwnedRules || isBusy"
(click)="onSubmit()">
{{ 'ACA_FOLDER_RULES.LINK_RULES_DIALOG.SUBMIT' | translate }}
</button>
</mat-dialog-actions>
@@ -0,0 +1,76 @@
.aca-rule-set-picker-container {
--rule-set-picker-padding: 8px 20px;
.mat-dialog-container {
padding: 0;
}
}
.aca-rule-set-picker {
&__header {
display: flex;
align-items: center;
margin: 0;
padding: var(--rule-set-picker-padding);
box-sizing: border-box;
border-bottom: 1px solid var(--theme-border-color);
&__title {
font-size: 16px;
font-weight: bold;
flex-grow: 1;
}
&__close {
& mat-icon {
font-size: 18px;
}
}
}
&__content {
position: relative;
height: 80vh;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 2fr minmax(250px, 1fr);
&__node-selector {
padding: 0 20px;
box-sizing: border-box;
}
&__rule-list {
padding: 0 20px 0 0;
display: flex;
align-items: stretch;
flex-direction: column;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
&.justify {
align-items: center;
justify-content: center;
}
&__header {
color: var(--theme-text-color);
font-size: 0.9em;
margin: 8px 0;
}
.aca-rule-list-item {
cursor: default;
}
}
}
&__footer {
margin: 0;
padding: var(--rule-set-picker-padding);
box-sizing: border-box;
border-top: 1px solid var(--theme-border-color);
}
}
@@ -0,0 +1,140 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RuleSetPickerOptions, RuleSetPickerSmartComponent } from './rule-set-picker.smart-component';
import { CoreTestingModule } from '@alfresco/adf-core';
import { folderToLinkMock, otherFolderMock } from '../mock/node.mock';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { of } from 'rxjs';
import { ownedRuleSetMock, ruleSetWithLinkMock, ruleSetWithNoRulesToLinkMock, ruleSetWithOwnedRulesToLinkMock } from '../mock/rule-sets.mock';
import { ContentApiService } from '@alfresco/aca-shared';
import { By } from '@angular/platform-browser';
describe('RuleSetPickerSmartComponent', () => {
let fixture: ComponentFixture<RuleSetPickerSmartComponent>;
let component: RuleSetPickerSmartComponent;
let folderRuleSetsService: FolderRuleSetsService;
let loadRuleSetsSpy: jasmine.Spy;
let callApiSpy: jasmine.Spy;
const dialogRef = {
close: jasmine.createSpy('close'),
open: jasmine.createSpy('open')
};
const dialogOptions: RuleSetPickerOptions = {
nodeId: 'folder-1-id',
defaultNodeId: 'folder-1-id'
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: dialogOptions },
{
provide: ContentApiService,
useValue: {
getNode: () => {
return of({ entry: folderToLinkMock });
},
getNodeInfo: () => {
return of(otherFolderMock);
}
}
}
]
});
folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
fixture = TestBed.createComponent(RuleSetPickerSmartComponent);
component = fixture.componentInstance;
component['folderRuleSetsService'] = folderRuleSetsService;
loadRuleSetsSpy = spyOn(folderRuleSetsService, 'loadRuleSets').and.callThrough();
callApiSpy = spyOn<any>(folderRuleSetsService, 'callApi');
callApiSpy
.withArgs(`/nodes/${dialogOptions.nodeId}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${dialogOptions.nodeId}/rule-sets/-default-?include=isLinkedTo,owningFolder,linkedToBy`, 'GET')
.and.returnValue(Promise.resolve(ownedRuleSetMock))
.withArgs(`/nodes/${folderToLinkMock.id}?include=path%2Cproperties%2CallowableOperations%2Cpermissions`, 'GET')
.and.returnValue(Promise.resolve({ entry: folderToLinkMock }));
});
afterEach(() => {
fixture.destroy();
});
it('should load the rule sets of a node once it has been selected', () => {
expect(loadRuleSetsSpy).not.toHaveBeenCalled();
component.onNodeSelect([folderToLinkMock]);
expect(loadRuleSetsSpy).toHaveBeenCalledWith(folderToLinkMock.id, false);
component.onNodeSelect([folderToLinkMock]);
expect(loadRuleSetsSpy).toHaveBeenCalledTimes(1);
});
it('should show an empty list message if a selected folder has no rules', () => {
component.mainRuleSet$ = of(ruleSetWithNoRulesToLinkMock);
component.rulesLoading$ = of(false);
component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item'));
expect(items.length).toBe(0);
const emptyList = fixture.debugElement.query(By.css('adf-empty-content'));
expect(emptyList).not.toBeNull();
});
it('should show an empty list message if a selected folder has linked rules', () => {
component.mainRuleSet$ = of(ruleSetWithLinkMock);
component.rulesLoading$ = of(false);
component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item'));
expect(items.length).toBe(0);
const emptyList = fixture.debugElement.query(By.css('adf-empty-content'));
expect(emptyList).not.toBeNull();
});
it('should show a list of items if a selected folder has owned rules', () => {
component.mainRuleSet$ = of(ruleSetWithOwnedRulesToLinkMock);
component.rulesLoading$ = of(false);
component.onNodeSelect([folderToLinkMock]);
fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.aca-rule-set-picker__content__rule-list aca-rule-list-item'));
expect(items.length).toBe(2);
const emptyList = fixture.debugElement.query(By.css('adf-empty-content'));
expect(emptyList).toBeNull();
});
});
@@ -0,0 +1,120 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FolderRuleSetsService } from '../services/folder-rule-sets.service';
import { Node } from '@alfresco/js-api';
import { RuleSet } from '../model/rule-set.model';
import { BehaviorSubject, combineLatest, from, of, Subject } from 'rxjs';
import { finalize, map, switchMap, takeUntil } from 'rxjs/operators';
import { NotificationService } from '@alfresco/adf-core';
export interface RuleSetPickerOptions {
nodeId: string;
defaultNodeId: string;
existingRuleSet?: RuleSet;
}
@Component({
selector: 'aca-rule-set-picker',
templateUrl: './rule-set-picker.smart-component.html',
styleUrls: ['./rule-set-picker.smart-component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-rule-set-picker' },
providers: [FolderRuleSetsService]
})
export class RuleSetPickerSmartComponent implements OnInit, OnDestroy {
nodeId = '-root-';
defaultNodeId = '-root-';
isBusy = false;
existingRuleSet: RuleSet = null;
hasOwnedRules = false;
private selectedNodeId = '';
private folderLoading$ = new BehaviorSubject<boolean>(true);
mainRuleSet$ = this.folderRuleSetsService.mainRuleSet$;
rulesLoading$ = combineLatest(this.folderRuleSetsService.isLoading$, this.folderLoading$).pipe(
map(([rulesLoading, folderLoading]) => rulesLoading || folderLoading)
);
onDestroy$ = new Subject<void>();
constructor(
@Inject(MAT_DIALOG_DATA) public data: RuleSetPickerOptions,
private folderRuleSetsService: FolderRuleSetsService,
private dialogRef: MatDialogRef<RuleSetPickerSmartComponent>,
private notificationService: NotificationService
) {
this.nodeId = this.data?.nodeId ?? '-root-';
this.defaultNodeId = this.data?.defaultNodeId ?? '-root-';
this.existingRuleSet = this.data?.existingRuleSet ?? null;
}
ngOnInit(): void {
this.mainRuleSet$.pipe(takeUntil(this.onDestroy$)).subscribe((mainRuleSet) => {
this.hasOwnedRules = mainRuleSet?.rules.length > 0 && FolderRuleSetsService.isOwnedRuleSet(mainRuleSet, this.selectedNodeId);
});
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
onNodeSelect(nodes: Node[]) {
if (nodes?.length && nodes[0].isFolder && nodes[0].id !== this.selectedNodeId) {
this.selectedNodeId = nodes[0].id;
this.folderRuleSetsService.loadRuleSets(this.selectedNodeId, false);
}
}
setFolderLoading(isLoading: boolean) {
this.folderLoading$.next(isLoading);
}
onSubmit() {
this.isBusy = true;
from(this.existingRuleSet ? this.folderRuleSetsService.deleteRuleSetLink(this.nodeId, this.existingRuleSet.id) : of(null))
.pipe(
switchMap(() => from(this.folderRuleSetsService.createRuleSetLink(this.nodeId, this.selectedNodeId))),
finalize(() => {
this.isBusy = false;
})
)
.subscribe(
() => {
this.dialogRef.close(true);
},
() => {
this.handleError();
}
);
}
private handleError() {
this.notificationService.showError('ACA_FOLDER_RULES.LINK_RULES_DIALOG.ERRORS.REQUEST_FAILED');
}
}
@@ -0,0 +1,93 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ActionsService } from './actions.service';
import { TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { ActionsApi } from '@alfresco/js-api';
import { actionDefListMock, actionsTransformedListMock } from '../mock/actions.mock';
import { take } from 'rxjs/operators';
import { dummyConstraints, rawConstraints } from '../mock/action-parameter-constraints.mock';
import { of } from 'rxjs';
describe('ActionsService', () => {
let actionsService: ActionsService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
providers: [ActionsService]
});
actionsService = TestBed.inject(ActionsService);
});
it('should load the data into the observable', async () => {
spyOn(ActionsApi.prototype, 'listActions').and.returnValue(Promise.resolve(actionDefListMock));
const actionsPromise = actionsService.actionDefinitionsListing$.pipe(take(2)).toPromise();
actionsService.loadActionDefinitions();
const actionsList = await actionsPromise;
expect(actionsList).toEqual(actionsTransformedListMock);
});
it('should set loading to true while the request is being sent', async () => {
spyOn(ActionsApi.prototype, 'listActions').and.returnValue(Promise.resolve(actionDefListMock));
const loadingTruePromise = actionsService.loading$.pipe(take(2)).toPromise();
const loadingFalsePromise = actionsService.loading$.pipe(take(3)).toPromise();
actionsService.loadActionDefinitions();
expect(await loadingTruePromise).toBeTrue();
expect(await loadingFalsePromise).toBeFalse();
});
it('loadParameterConstraints should send GET request and return formatted observable', async () => {
const params = [{}, {}, {}, {}, {}, ['application/json'], ['application/json']];
const constraintName = dummyConstraints[0].name;
const formattedConstraints = dummyConstraints[0].constraints;
const apiCallSpy = spyOn<any>(actionsService, 'publicApiCall')
.withArgs(`/action-parameter-constraints/${constraintName}`, 'GET', params)
.and.returnValue(of(rawConstraints));
actionsService.getParameterConstraints(constraintName).subscribe((result) => expect(result).toEqual(formattedConstraints));
expect(apiCallSpy).toHaveBeenCalled();
expect(apiCallSpy).toHaveBeenCalledWith(`/action-parameter-constraints/${constraintName}`, 'GET', params);
});
it('loadActionParameterConstraints should load the data into the observable', async () => {
spyOn(actionsService, 'getParameterConstraints').and.returnValue(of(dummyConstraints[0].constraints));
const constraintsPromise = actionsService.parameterConstraints$.pipe(take(2)).toPromise();
actionsService.loadActionParameterConstraints(actionsTransformedListMock);
const parameterConstraints = await constraintsPromise;
expect(parameterConstraints).toEqual(dummyConstraints);
});
});
@@ -0,0 +1,139 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { ActionDefinition, ActionDefinitionEntry, ActionDefinitionList, ActionsApi } from '@alfresco/js-api';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { BehaviorSubject, forkJoin, from, Observable, of } from 'rxjs';
import { finalize, map, switchMap } from 'rxjs/operators';
import { ActionDefinitionTransformed, ActionParameterDefinitionTransformed } from '../model/rule-action.model';
import { ActionParameterConstraint, ConstraintValue } from '../model/action-parameter-constraint.model';
@Injectable({ providedIn: 'root' })
export class ActionsService {
private actionDefinitionsListingSource = new BehaviorSubject<ActionDefinitionTransformed[]>([]);
actionDefinitionsListing$ = this.actionDefinitionsListingSource.asObservable();
private loadingSource = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSource.asObservable();
private parameterConstraintsSource = new BehaviorSubject<ActionParameterConstraint[]>([]);
parameterConstraints$: Observable<ActionParameterConstraint[]> = this.parameterConstraintsSource.asObservable();
private _actionsApi: ActionsApi;
get actionsApi(): ActionsApi {
if (!this._actionsApi) {
this._actionsApi = new ActionsApi(this.apiService.getInstance());
}
return this._actionsApi;
}
constructor(private apiService: AlfrescoApiService) {}
loadActionDefinitions() {
this.loadingSource.next(true);
from(this.actionsApi.listActions())
.pipe(
map((list: ActionDefinitionList) => list.list.entries.map((entry) => this.transformActionDefinition(entry))),
finalize(() => this.loadingSource.next(false))
)
.subscribe((obj: ActionDefinitionTransformed[]) => {
this.actionDefinitionsListingSource.next(obj);
});
}
getParameterConstraints(constraintName): Observable<ConstraintValue[]> {
return from(
this.publicApiCall(`/action-parameter-constraints/${constraintName}`, 'GET', [{}, {}, {}, {}, {}, ['application/json'], ['application/json']])
).pipe(map((res) => res.entry.constraintValues.map((entry) => this.formatConstraint(entry))));
}
private transformActionDefinition(obj: ActionDefinition | ActionDefinitionEntry): ActionDefinitionTransformed {
if (this.isActionDefinitionEntry(obj)) {
obj = obj.entry;
}
return {
id: obj.id,
name: obj.name ?? '',
description: obj.description ?? '',
title: obj.title ?? obj.name ?? '',
applicableTypes: obj.applicableTypes ?? [],
trackStatus: obj.trackStatus ?? false,
parameterDefinitions: (obj.parameterDefinitions ?? []).map((paramDef: ActionParameterDefinitionTransformed) =>
this.transformActionParameterDefinition(paramDef)
)
};
}
private transformActionParameterDefinition(obj: ActionParameterDefinitionTransformed): ActionParameterDefinitionTransformed {
return {
name: obj.name ?? '',
type: obj.type ?? '',
multiValued: obj.multiValued ?? false,
mandatory: obj.mandatory ?? false,
displayLabel: obj.displayLabel ?? obj.name ?? '',
parameterConstraintName: obj.parameterConstraintName ?? ''
};
}
private isActionDefinitionEntry(obj): obj is ActionDefinitionEntry {
return typeof obj.entry !== 'undefined';
}
private publicApiCall(path: string, httpMethod: string, params?: any[]): Promise<any> {
return this.apiService.getInstance().contentClient.callApi(path, httpMethod, ...params);
}
private formatConstraint(constraint): ConstraintValue {
return {
value: constraint.value ?? '',
label: constraint.label ?? ''
};
}
loadActionParameterConstraints(actionDefinitions: ActionDefinitionTransformed[]): void {
of(actionDefinitions)
.pipe(
map((actionDefinition) =>
actionDefinition
.map((obj) => obj.parameterDefinitions)
.flat()
.filter((parameterDefinition) => parameterDefinition.parameterConstraintName.length > 0)
.map((parameterDefinition) => ({
name: parameterDefinition.name,
parameterConstraintName: parameterDefinition.parameterConstraintName,
constraints: null
}))
),
switchMap((parameterDefinitions) =>
forkJoin(
...parameterDefinitions.map((parameterDefinition) =>
this.getParameterConstraints(parameterDefinition.parameterConstraintName).pipe(
map((constraints) => ({ name: parameterDefinition.name, constraints }))
)
)
)
)
)
.subscribe((res) => this.parameterConstraintsSource.next(res));
}
}
@@ -0,0 +1,152 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { FolderRuleSetsService } from './folder-rule-sets.service';
import { TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
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 { getDefaultRuleSetResponseMock, getRuleSetsResponseMock, inheritedRuleSetMock, ownedRuleSetMock } from '../mock/rule-sets.mock';
import { take } from 'rxjs/operators';
import { inheritedRulesMock, linkedRulesMock, ownedRulesMock, ruleMock } from '../mock/rules.mock';
describe('FolderRuleSetsService', () => {
let folderRuleSetsService: FolderRuleSetsService;
let folderRulesService: FolderRulesService;
let contentApiService: ContentApiService;
let callApiSpy: jasmine.Spy;
let getRulesSpy: jasmine.Spy;
let getNodeSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
providers: [FolderRuleSetsService, FolderRulesService, ContentApiService]
});
folderRuleSetsService = TestBed.inject(FolderRuleSetsService);
folderRulesService = TestBed.inject(FolderRulesService);
contentApiService = TestBed.inject(ContentApiService);
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));
getRulesSpy = spyOn<any>(folderRulesService, 'getRules')
.withArgs(jasmine.anything(), 'rule-set-no-links')
.and.returnValue(of({ rules: ownedRulesMock, hasMoreRules: false }))
.withArgs(jasmine.anything(), 'rule-set-with-link')
.and.returnValue(of({ rules: linkedRulesMock, hasMoreRules: false }))
.withArgs(jasmine.anything(), 'inherited-rule-set')
.and.returnValue(of({ rules: inheritedRulesMock, hasMoreRules: false }));
getNodeSpy = spyOn(contentApiService, 'getNode')
.withArgs(owningFolderIdMock)
.and.returnValue(of(getOwningFolderEntryMock))
.withArgs(otherFolderIdMock)
.and.returnValue(of(getOtherFolderEntryMock));
});
it(`should load node info when loading the node's rule sets`, async () => {
// take(2), because: 1 = init of the BehaviourSubject, 2 = in subscribe
const folderInfoPromise = folderRuleSetsService.folderInfo$.pipe(take(2)).toPromise();
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
const folderInfo = await folderInfoPromise;
expect(getNodeSpy).toHaveBeenCalledWith(owningFolderIdMock);
expect(folderInfo).toEqual(owningFolderMock);
});
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 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 inheritedRuleSetsPromise;
const hasMoreRuleSets = await hasMoreRuleSetsPromise;
expect(callApiSpy).toHaveBeenCalledWith(
`/nodes/${owningFolderIdMock}/rule-sets?include=isLinkedTo,owningFolder,linkedToBy&skipCount=0&maxItems=100`,
'GET'
);
expect(ruleSets).toEqual([inheritedRuleSetMock]);
expect(getRulesSpy).toHaveBeenCalledWith(owningFolderIdMock, jasmine.anything());
expect(hasMoreRuleSets).toEqual(false);
});
it('should select the first rule of the owned rule set of the folder', async () => {
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.inheritedRuleSets$.pipe(take(3)).toPromise();
folderRuleSetsService.loadRuleSets(owningFolderIdMock);
await ruleSetListingPromise;
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('owned-rule-1'));
});
it('should select a different rule when removing a rule', () => {
const selectRuleSpy = spyOn(folderRulesService, 'selectRule');
folderRuleSetsService['mainRuleSet'] = JSON.parse(JSON.stringify(ownedRuleSetMock));
folderRuleSetsService['inheritedRuleSets'] = JSON.parse(JSON.stringify([inheritedRuleSetMock]));
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-1-id');
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('owned-rule-2'));
selectRuleSpy.calls.reset();
folderRuleSetsService.removeRuleFromMainRuleSet('owned-rule-2-id');
expect(selectRuleSpy).toHaveBeenCalledWith(ruleMock('inherited-rule-1'));
});
it('should send a POST request to create a new link between two folders', () => {
folderRuleSetsService.createRuleSetLink('folder-1-id', 'folder-2-id');
expect(callApiSpy).toHaveBeenCalledWith('/nodes/folder-1-id/rule-set-links', 'POST', {
id: 'folder-2-id'
});
});
it('should send a DELETE request to delete a link between two folders', () => {
folderRuleSetsService.deleteRuleSetLink('folder-1-id', 'rule-set-1-id');
expect(callApiSpy).toHaveBeenCalledWith('/nodes/folder-1-id/rule-set-links/rule-set-1-id', 'DELETE');
});
});
@@ -0,0 +1,255 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { BehaviorSubject, combineLatest, from, Observable, of } from 'rxjs';
import { NodeInfo } from '@alfresco/aca-shared/store';
import { catchError, finalize, map, switchMap, tap } from 'rxjs/operators';
import { RuleSet } from '../model/rule-set.model';
import { ContentApiService } from '@alfresco/aca-shared';
import { NodeEntry } from '@alfresco/js-api';
import { FolderRulesService } from './folder-rules.service';
import { Rule } from '../model/rule.model';
@Injectable({
providedIn: 'root'
})
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 mainRuleSet: RuleSet = null;
private inheritedRuleSets: RuleSet[] = [];
private hasMoreRuleSets = true;
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);
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$: Observable<boolean> = this.isLoadingSource.asObservable();
selectedRuleSet$ = this.folderRulesService.selectedRule$.pipe(
map((rule: Rule) => {
if (rule === null) {
return null;
}
if (this.mainRuleSet?.rules.findIndex((r: Rule) => r.id === rule.id) > -1) {
return this.mainRuleSet;
}
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> {
// APIs used by this service are still private and not yet available for public use
const params = [{}, {}, {}, {}, body, ['application/json'], ['application/json']];
return this.apiService.getInstance().contentPrivateClient.callApi(path, httpMethod, ...params);
}
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}`,
'GET'
)
).pipe(
tap((res) => {
if (res?.list?.pagination) {
this.hasMoreRuleSets = res.list.pagination.hasMoreItems;
}
}),
switchMap((res) => this.formatRuleSets(res)),
map((ruleSets: RuleSet[]) => ruleSets.filter((ruleSet) => FolderRuleSetsService.isInheritedRuleSet(ruleSet, this.currentFolder.id)))
);
}
loadRuleSets(nodeId: string, loadInheritedRuleSets = true) {
this.isLoadingSource.next(true);
this.mainRuleSet = null;
this.inheritedRuleSets = [];
this.hasMoreRuleSets = true;
this.currentFolder = null;
this.mainRuleSetSource.next(this.mainRuleSet);
this.inheritedRuleSetsSource.next(this.inheritedRuleSets);
this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
this.getNodeInfo(nodeId)
.pipe(
tap((nodeInfo: NodeInfo) => {
this.currentFolder = nodeInfo;
this.folderInfoSource.next(this.currentFolder);
}),
switchMap(() => combineLatest(this.getMainRuleSet(nodeId), loadInheritedRuleSets ? this.getInheritedRuleSets(nodeId) : of([]))),
finalize(() => this.isLoadingSource.next(false))
)
.subscribe(([mainRuleSet, inheritedRuleSets]) => {
this.mainRuleSet = mainRuleSet;
this.inheritedRuleSets = inheritedRuleSets;
this.mainRuleSetSource.next(mainRuleSet);
this.inheritedRuleSetsSource.next(inheritedRuleSets);
this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
const ruleToSelect =
mainRuleSet?.rules.find((r: Rule) => FolderRuleSetsService.isOwnedRuleSet(mainRuleSet, nodeId) || r.isEnabled) ??
inheritedRuleSets.reduce((foundRule: Rule, ruleSet: RuleSet) => foundRule ?? ruleSet.rules.find((r: Rule) => r.isEnabled), null);
this.folderRulesService.selectRule(ruleToSelect);
});
}
loadMoreInheritedRuleSets() {
this.isLoadingSource.next(true);
this.getInheritedRuleSets(this.currentFolder.id, this.inheritedRuleSets.length)
.pipe(finalize(() => this.isLoadingSource.next(false)))
.subscribe((ruleSets) => {
this.inheritedRuleSets.push(...ruleSets);
this.inheritedRuleSetsSource.next(this.inheritedRuleSets);
this.hasMoreRuleSetsSource.next(this.hasMoreRuleSets);
});
}
private getNodeInfo(nodeId: string): Observable<NodeInfo> {
if (nodeId) {
return this.contentApi.getNode(nodeId).pipe(
catchError((error) => {
if (error.status === 404) {
return of({ entry: null });
}
return of(error);
}),
map((entry: NodeEntry) => entry.entry)
);
} else {
return of(null);
}
}
private formatRuleSets(res: any): Observable<RuleSet[]> {
return res?.list?.entries && res.list.entries instanceof Array
? combineLatest((res.list.entries as Array<any>).map((entry) => this.formatRuleSet(entry.entry)))
: of([]);
}
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(this.currentFolder.id || '', entry.id)
).pipe(
map(([owningFolderNodeInfo, getRulesRes]) => ({
id: entry.id,
isLinkedTo: entry.isLinkedTo || false,
owningFolder: owningFolderNodeInfo,
linkedToBy: entry.linkedToBy || [],
rules: getRulesRes.rules,
hasMoreRules: getRulesRes.hasMoreRules,
loadingRules: false
}))
);
}
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);
}
this.folderRulesService.selectRule(this.mainRuleSet?.rules[0] ?? this.inheritedRuleSets[0]?.rules[0] ?? 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.refreshMainRuleSet(newRule);
}
}
refreshMainRuleSet(ruleToSelect: Rule = null) {
this.getMainRuleSet(this.currentFolder.id).subscribe((mainRuleSet: RuleSet) => {
this.mainRuleSet = { ...mainRuleSet };
this.mainRuleSetSource.next(this.mainRuleSet);
if (mainRuleSet) {
const ruleToSelectInRuleSet = ruleToSelect ? mainRuleSet.rules.find((rule: Rule) => rule.id === ruleToSelect.id) : mainRuleSet.rules[0];
this.folderRulesService.selectRule(ruleToSelectInRuleSet);
}
});
}
async createRuleSetLink(folderIdToCreateLink: string, folderIdToLinkFrom: string): Promise<unknown> {
const data = {
id: folderIdToLinkFrom
};
return this.callApi(`/nodes/${folderIdToCreateLink}/rule-set-links`, 'POST', data);
}
async deleteRuleSetLink(folderIdToDeleteLink: string, ruleSetIdToDelete: string): Promise<unknown> {
return this.callApi(`/nodes/${folderIdToDeleteLink}/rule-set-links/${ruleSetIdToDelete}`, 'DELETE');
}
}
@@ -0,0 +1,163 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { FolderRulesService } from './folder-rules.service';
import {
getMoreRulesResponseMock,
getRulesResponseMock,
manyRulesMock,
moreRulesMock,
ruleMock,
ruleSettingsMock,
rulesMock
} from '../mock/rules.mock';
import { ruleSetMock } from '../mock/rule-sets.mock';
import { owningFolderIdMock } from '../mock/node.mock';
import { take } from 'rxjs/operators';
describe('FolderRulesService', () => {
let folderRulesService: FolderRulesService;
let callApiSpy: jasmine.Spy;
const nodeId = owningFolderIdMock;
const ruleSetId = 'rule-set-id';
const mockedRule = ruleMock('rule-mock');
const { ...mockedRuleWithoutId } = mockedRule;
const mockedRuleEntry = { entry: mockedRule };
const ruleId = mockedRule.id;
const mockedRuleSettingsEntry = { entry: ruleSettingsMock };
const key = ruleSettingsMock.key;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule],
providers: [FolderRulesService]
});
folderRulesService = TestBed.inject<FolderRulesService>(FolderRulesService);
callApiSpy = spyOn<any>(folderRulesService, 'callApi');
});
it('should load some rules into a rule set', () => {
const ruleSet = ruleSetMock();
callApiSpy.and.returnValue(of(getRulesResponseMock));
expect(ruleSet.rules.length).toBe(0);
expect(ruleSet.hasMoreRules).toBeTrue();
expect(ruleSet.loadingRules).toBeFalse();
folderRulesService.loadRules(ruleSet);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=0&maxItems=100`, 'GET');
expect(ruleSet.rules.length).toBe(2);
expect(ruleSet.rules).toEqual(rulesMock);
expect(ruleSet.hasMoreRules).toBeFalse();
});
it('should load more rules if it still has some more to load', () => {
const ruleSet = ruleSetMock(rulesMock);
callApiSpy.and.returnValue(of(getMoreRulesResponseMock));
expect(ruleSet.rules.length).toBe(2);
expect(ruleSet.hasMoreRules).toBeTrue();
folderRulesService.loadRules(ruleSet);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${ruleSet.owningFolder.id}/rule-sets/${ruleSet.id}/rules?skipCount=2&maxItems=100`, 'GET');
expect(ruleSet.rules.length).toBe(4);
expect(ruleSet.rules).toEqual([...rulesMock, ...moreRulesMock]);
expect(ruleSet.hasMoreRules).toBeFalse();
});
it('should select the right rule rule after loading', () => {
const ruleSet = ruleSetMock(rulesMock);
spyOn(folderRulesService, 'getRules').and.returnValue(of({ rules: manyRulesMock, hasMoreRules: false }));
const selectedRuleSourceSpy = spyOn(folderRulesService['selectedRuleSource'], 'next');
folderRulesService.loadRules(ruleSet, 0);
expect(selectedRuleSourceSpy).not.toHaveBeenCalled();
folderRulesService.loadRules(ruleSet, 0, 'first');
expect(selectedRuleSourceSpy).toHaveBeenCalledWith(ruleMock('rule1'));
selectedRuleSourceSpy.calls.reset();
folderRulesService.loadRules(ruleSet, 0, 'last');
expect(selectedRuleSourceSpy).toHaveBeenCalledWith(ruleMock('rule5'));
selectedRuleSourceSpy.calls.reset();
selectedRuleSourceSpy.calls.reset();
folderRulesService.loadRules(ruleSet, 0, ruleMock('rule3'));
expect(selectedRuleSourceSpy).toHaveBeenCalledWith(ruleMock('rule3'));
});
it('should delete a rule and return its id', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE').and.returnValue(ruleId);
const deletedRulePromise = folderRulesService.deletedRuleId$.pipe(take(2)).toPromise();
folderRulesService.deleteRule(nodeId, ruleId, ruleSetId);
const deletedRule = await deletedRulePromise;
expect(deletedRule).toBeTruthy('rule has not been deleted');
expect(deletedRule).toBe(ruleId, 'wrong id of deleted rule');
expect(callApiSpy).toHaveBeenCalledTimes(1);
expect(callApiSpy).toHaveBeenCalledWith(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE');
});
it('should send correct POST request and return created rule', async () => {
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(mockedRuleEntry));
const result = await folderRulesService.updateRule(nodeId, ruleId, mockedRule, ruleSetId);
expect(result).toEqual(mockedRule);
});
it('should send correct GET request and return rule settings', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'GET').and.returnValue(Promise.resolve(mockedRuleSettingsEntry));
const result = await folderRulesService.getRuleSettings(nodeId, key);
expect(result).toEqual(ruleSettingsMock);
});
it('should send correct PUT request to update rule settings and return them', async () => {
callApiSpy.withArgs(`/nodes/${nodeId}/rule-settings/${key}`, 'PUT', ruleSettingsMock).and.returnValue(Promise.resolve(mockedRuleSettingsEntry));
const result = await folderRulesService.updateRuleSettings(nodeId, key, ruleSettingsMock);
expect(result).toEqual(ruleSettingsMock);
});
});
@@ -0,0 +1,225 @@
/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { Rule, RuleForForm, RuleOptions, RuleSettings } from '../model/rule.model';
import { RuleCompositeCondition } from '../model/rule-composite-condition.model';
import { RuleSimpleCondition } from '../model/rule-simple-condition.model';
import { RuleSet } from '../model/rule-set.model';
interface GetRulesResult {
rules: Rule[];
hasMoreRules: boolean;
}
@Injectable({
providedIn: 'root'
})
export class FolderRulesService {
public static MAX_RULES_PER_GET = 100;
public static get emptyCompositeCondition(): RuleCompositeCondition {
return {
inverted: false,
booleanMode: 'and',
compositeConditions: [],
simpleConditions: []
};
}
public static get emptyRuleOptions(): RuleOptions {
return {
isEnabled: true,
isInheritable: false,
isAsynchronous: false,
errorScript: ''
};
}
public static get emptyRule(): Rule {
return {
id: '',
name: '',
description: '',
isShared: false,
triggers: ['inbound'],
conditions: FolderRulesService.emptyCompositeCondition,
actions: [],
...FolderRulesService.emptyRuleOptions
};
}
public static get emptyRuleForForm(): RuleForForm {
const value = {
...FolderRulesService.emptyRule,
options: FolderRulesService.emptyRuleOptions
};
Object.keys(value.options).forEach((key: string) => {
delete value[key];
});
return value;
}
private selectedRuleSource = new BehaviorSubject<Rule>(null);
private deletedRuleIdSource = new BehaviorSubject<string>(null);
selectedRule$ = this.selectedRuleSource.asObservable();
deletedRuleId$: Observable<string> = this.deletedRuleIdSource.asObservable();
constructor(private apiService: AlfrescoApiService) {}
private callApi(path: string, httpMethod: string, body: object = {}): Promise<any> {
// APIs used by this service are still private and not yet available for public use
const params = [{}, {}, {}, {}, body, ['application/json'], ['application/json']];
return this.apiService.getInstance().contentPrivateClient.callApi(path, httpMethod, ...params);
}
getRules(owningFolderId: string, ruleSetId: string, skipCount = 0): Observable<GetRulesResult> {
return from(
this.callApi(
`/nodes/${owningFolderId}/rule-sets/${ruleSetId}/rules?skipCount=${skipCount}&maxItems=${FolderRulesService.MAX_RULES_PER_GET}`,
'GET'
)
).pipe(
map((res) => ({
rules: this.formatRules(res),
hasMoreRules: !!res?.list?.pagination?.hasMoreItems
}))
);
}
loadRules(ruleSet: RuleSet, skipCount = ruleSet.rules.length, selectRule: 'first' | 'last' | Rule = null) {
if (ruleSet && !ruleSet.loadingRules) {
ruleSet.loadingRules = true;
this.getRules(ruleSet.owningFolder.id, ruleSet.id, skipCount)
.pipe(
finalize(() => {
ruleSet.loadingRules = false;
})
)
.subscribe((res: GetRulesResult) => {
ruleSet.hasMoreRules = res.hasMoreRules;
ruleSet.rules.splice(skipCount);
ruleSet.rules.push(...res.rules);
this.selectRuleInRuleSet(ruleSet, selectRule);
});
}
}
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);
}
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-') {
from(this.callApi(`/nodes/${nodeId}/rule-sets/${ruleSetId}/rules/${ruleId}`, 'DELETE')).subscribe(
() => {
this.deletedRuleIdSource.next(ruleId);
},
(error) => {
this.deletedRuleIdSource.next(error);
}
);
}
async getRuleSettings(nodeId: string, key: string = '-isInheritanceEnabled-'): Promise<RuleSettings> {
const response = await this.callApi(`/nodes/${nodeId}/rule-settings/${key}`, 'GET');
return response.entry;
}
async updateRuleSettings(nodeId: string, key: string, body: RuleSettings): Promise<RuleSettings> {
const response = await this.callApi(`/nodes/${nodeId}/rule-settings/${key}`, 'PUT', { ...body });
return response.entry;
}
private formatRules(res): Rule[] {
return [...res.list.entries.map((entry) => this.formatRule(entry.entry))];
}
private formatRule(obj): Rule {
return {
id: obj.id,
name: obj.name ?? FolderRulesService.emptyRule.name,
description: obj.description ?? FolderRulesService.emptyRule.description,
isEnabled: obj.isEnabled ?? FolderRulesService.emptyRule.isEnabled,
isInheritable: obj.isInheritable ?? FolderRulesService.emptyRule.isInheritable,
isAsynchronous: obj.isAsynchronous ?? FolderRulesService.emptyRule.isAsynchronous,
errorScript: obj.errorScript ?? FolderRulesService.emptyRule.errorScript,
isShared: obj.isShared ?? FolderRulesService.emptyRule.isShared,
triggers: obj.triggers ?? FolderRulesService.emptyRule.triggers,
conditions: this.formatCompositeCondition(obj.conditions ?? { ...FolderRulesService.emptyRule.conditions }),
actions: obj.actions ?? FolderRulesService.emptyRule.actions
};
}
private formatCompositeCondition(obj): RuleCompositeCondition {
return {
inverted: obj.inverted ?? false,
booleanMode: obj.booleanMode ?? 'and',
compositeConditions: (obj.compositeConditions || []).map((condition) => this.formatCompositeCondition(condition)),
simpleConditions: this.parseSimpleCondition(obj.simpleConditions)
};
}
private parseSimpleCondition(arr) {
if (arr) {
if (arr.every((element) => element === null)) {
return [];
}
return arr.map((condition) => this.formatSimpleCondition(condition));
} else {
return [];
}
}
private formatSimpleCondition(obj): RuleSimpleCondition {
return {
field: obj.field || 'cm:name',
comparator: obj.comparator || 'equals',
parameter: obj.parameter || ''
};
}
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);
}
}
}
+1
View File
@@ -11,6 +11,7 @@
"input": "./src/lib/ui/",
"output": "ui"
},
"folder-rules/assets",
"about/assets",
"ms-office/assets",
"assets"
+12 -1
View File
@@ -4,7 +4,18 @@
"license": "LGPL-3.0",
"peerDependencies": {
"@angular/common": "^14.1.0",
"@angular/core": "^14.1.0"
"@angular/core": "^14.1.0",
"@alfresco/adf-core": "^6.1.0-0",
"@alfresco/adf-content-services": "^6.1.0-0",
"@alfresco/adf-extensions": "^6.1.0-0",
"@alfresco/js-api": "^6.1.0-0",
"@angular/animations": "^14.1.3",
"@angular/cdk": "^14.1.3",
"@angular/forms": "^14.1.3",
"@angular/material": "^14.1.3",
"@ngx-translate/core": "^14.0.0",
"rxjs": "6.6.6",
"zone.js": "0.11.8"
},
"dependencies": {
"tslib": "^2.3.0"

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