[ACS-8694] Cleanup of visibility rules for extensions in ACA (#4140)

* [ACS-8694] Updated occurrences of visible in extensions.json to use arrays instead of single strings. Cleaned up rules in aca-content.module.ts

* [ACS-8694] Removed extra commas

* [ACS-8694] Broke down canDelete rule into seperate entities

* [ACS-8694] Fixed typo for notEmpty rule

* [ACS-8694] Fixed rule for edit offline

* [ACS-8694] Updated extension.schema.json

* [ACS-8694] Updated extension.schema.json

* [ACS-8694] Fixed rule for manage versions context menu item

* [ACS-8694] Fixed rule for manage versions and manage permissions

* [ACS-8694] Added rules.canManageFolderRules

* [ACS-8694] Fixed typo

* [ACS-8694] Updated visibility rules for folder rules and AOS plugin

* [ACS-8694] Updated extension.schema.json

* [ACS-8694] Updated existing rules to use !isTrashcan() instead of isNotTrashcan()

* [ACS-8694] folder-rules.plugin.json now uses arrays for controlling visibility

* [ACS-8694] Updated app.extensions.schema

* [ACS-8694] Removed unused rules

* [ACS-8694] Added unit tests for canToggleFileLock

* [ACS-8694] Added rules-list.md

* [ACS-8694] Revert unneeded project.json change

* [ACS-8694] Fixed toggleEditOffline rule

* [ACS-8694] Added migration guide (#4139)

* [ACS-8694] Added migration guide

* [ACS-8694] Fixed typo

* [ACS-8694] Added missing rule migration. Fixed incorrect rule migration. Fixed typos

* [ACS-8694] Code review finding - Replaced instance of any

* [ACS-8694] Code review finding - Updated rules.md. Removed duplication of rules list from rules-list.md. Added pointer to rules-list.md under tips section

* [ACS-8694] Fixed build issue

* [ACS-8694] Removed unneeded isNotDetails rule
This commit is contained in:
swapnil-verma-gl 2025-02-25 17:00:22 +05:30 committed by GitHub
parent 1dd00c9e4c
commit f1c4dcf45d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 907 additions and 1125 deletions

View File

@ -0,0 +1,142 @@
----
Title: Legacy Rules for ACA
----
# Legacy rules
Below is a list of all unused rules that were removed with ACA 5.1.1. You may use them as a reference in case your application/extension is still dependent on them,
however we do advise that you revisit those implementations and substitute them with a different approach, since these rules will no longer be supported.
```typescript
/**
* app.selection.file.canLock
* Checks if user can lock selected file.
*/
export const canLockFile = (context: RuleContext): boolean => !isWriteLocked(context) && canUpdateSelectedNode(context);
/**
* app.selection.file.canUnlock
* Checks if user can unlock selected file.
*/
export function canUnlockFile(context: RuleContext): boolean {
const { file } = context.selection;
return isWriteLocked(context) && (context.permissions.check(file?.entry, ['delete']) || isUserWriteLockOwner(context));
}
/**
* app.selection.file.isLockOwner
* Checks if the selected file has **write** or **read-only** locks specified,
* and that current user is the owner of the lock.
*/
export const isUserWriteLockOwner = (context: RuleContext): boolean =>
isWriteLocked(context) &&
context.selection.file?.entry.properties['cm:lockOwner'] &&
context.selection.file?.entry.properties['cm:lockOwner'].id === context.profile.id;
/**
* app.selection.file.canShare
* Checks if user can share selected file.
*/
export const canShareFile = (context: RuleContext): boolean =>
[context.selection.file, navigation.isNotTrashcan(context), repository.hasQuickShareEnabled(context), !isShared(context)].every(Boolean);
/**
* app.selection.canUnshare
* Checks if user can un-share selected nodes.
*/
export function canUnshareNodes(context: RuleContext): boolean {
if (hasSelection(context)) {
return context.permissions.check(context.selection.nodes, ['delete'], {
target: 'allowableOperationsOnTarget'
});
}
return false;
}
/**
* app.selection.file.isShared
* Checks if the selected file is already shared.
*/
export function isShared(context: RuleContext): boolean {
if (navigation.isSharedFiles(context) && context.selection.file) {
return true;
}
if (navigation.isNotTrashcan(context) && hasSelection(context) && context.selection.file) {
return !!context.selection.file.entry?.properties?.['qshare:sharedId'];
}
return false;
}
/**
* app.selection.isPrivateLibrary
* Checks if user has selected a **private** library (site)
*/
export function isPrivateLibrary(context: RuleContext): boolean {
return context.selection.library?.entry?.visibility === 'PRIVATE';
}
/**
* app.selection.hasNoLibraryRole
* Checks if the selected library has no **role** property defined.
*/
export const hasNoLibraryRole = (context: RuleContext): boolean => !hasLibraryRole(context);
/**
* app.navigation.isLibraryFiles
* Checks if a **Library Files** route is activated.
*/
export function isLibraryFiles(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/libraries');
}
/**
* app.navigation.isPersonalFiles
* Checks if a **Personal Files** route is activated.
*/
export function isPersonalFiles(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/personal-files');
}
/**
* app.navigation.isSharedPreview
* Checks if a **Shared Preview** route is activated.
*/
export function isSharedPreview(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/shared/preview/') || (url?.startsWith('/shared') && url?.includes('viewer:view'));
}
/**
* app.navigation.isFavoritesPreview
* Checks if a **Favorites Preview** route is activated.
*/
export function isFavoritesPreview(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/favorites/preview/') || (url?.startsWith('/favorites') && url?.includes('viewer:view'));
}
/**
* app.navigation.isSharedFileViewer
* Checks if a **Shared File Preview** route is activated.
*/
export function isSharedFileViewer(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/preview/s/');
}
/**
* repository.isQuickShareEnabled
* Checks if the quick share repository option is enabled or not.
*/
export const hasQuickShareEnabled = (context: RuleContext): boolean => context.repository.status.isQuickShareEnabled;
/**
* user.isAdmin
* Checks if user is admin.
*/
export const isAdmin = (context: RuleContext): boolean => context.profile.isAdmin;
```

View File

@ -0,0 +1,129 @@
---
Title: Rule Migration Guide
---
# Migrating rules to be ACA 5.1.1 compatible
As part of cleanup of the evaluators section in ACA 5.1.1, several rules from the application were removed and
replaced with their alternatives. The biggest change involves the preference to use array based visibility rules
instead of string based. What this means is that developers will now be able to provide a set of smaller visibility
rules via array, instead of having to provide one single rule, which would internally call multiple smaller rules
via code.
For e.g., for a rule `app.canManageFileVersions`, which was basically a combination of `hasFileSelection(app.selection.file)`,
`isNotTrashcan (app.navigation.isNotTrashcan)`, and `isNotLocked(app.selection.file.isNotLocked)`, earlier, the code
used to look something like -
app.extensions.json
```json
{
"rules": {
"visible": "canManageFileVersions"
}
}
```
aca-content.module.ts -
```ts
extensions.setEvaluators({
'app.canManageFileVersions': rules.canManageFileVersions,
'app.selection.file': rules.hasFileSelection,
'app.navigation.isNotTrashcan': rules.isNotTrashcan,
'app.navigation.isTrashcan': rules.isTrashcan,
'app.selection.file.isNotLocked': rules.isNotLocked
})
```
app.rules.ts -
```ts
import { isTrashcan } from './navigation.rules';
export const rules = {
canManageFileVersions: () => hasFileSelection() && isNotTrashcan() && isNotLocked(),
hasFileSelection: (context) => !!context?.selection?.file,
isTrashcan: (context) => context.navigation.startsWith('/trashcan'),
isNotTrashcan: (context) => !isTrashcan(context),
isNotLocked: (context) => context.selection.file.entry.isLocked || context.selection.file.entry.properties?.['cm:lockType'] === 'READ_ONLY_LOCK'
}
```
Now, the visibility of this extension unit can be controlled in more compact way -
```json
{
"rules": {
"visible": [
"app.selection.file",
"!app.navigation.isTrashcan",
"!app.selection.file.isLocked"
]
}
}
```
No further changes in aca-content.module.ts, or app.rules.ts is required, apart from removing the unused rules and functions
**NOTE**: Notice the use of the rule negation `!` in the app.navigation.isTrashcan rule above, thereby removing another unneeded rule.
Below is a full list of rules that would need to be migrated, along with the corresponding set of rules that can be used in their place. Do note that the default OOTB ACA already has the migrations applied on extensions.json, and the changes would
only need to be done on any extensions configuration that is custom to you. All old migrated rules have been removed from the code base, so performing this migration is necessary in order to ensure a stable Alfresco Content Application experience
| Old rule | New Rule |
|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| app.navigation.isNotTrashcan | !app.navigation.isTrashcan |
| app.navigation.isNotFavorites | !app.navigation.isFavorites |
| app.navigation.isNotSharedFiles | !app.navigation.isSharedFiles |
| app.navigation.isNotLibraries | !app.navigation.isLibraries |
| app.navigation.isNotRecentFiles | !app.navigation.isRecentFiles |
| app.navigation.isNotSearchResults | !app.navigation.isSearchResults |
| canViewFile | app.selection.file<br/>!app.navigation.isTrashcan |
| app.canCreateLibrary | app.isContentServiceEnabled<br/>app.navigation.isLibraries |
| app.selection.canDownload | app.selection.notEmpty<br/>!app.navigation.isTrashcan<br/>app.selection.canDownload |
| isTrashcanItemSelected | app.navigation.isTrashcan<br/>app.selection.notEmpty |
| canLeaveLibrary | app.selection.library<br/>app.selection.hasLibraryRole |
| canShowInfoDrawer | app.selection.notEmpty<br/>!app.navigation.isLibraries<br/>!app.navigation.isTrashcan |
| app.toolbar.favorite.canEditMetadata | app.selection.library<br/>isLibraryManager |
| canToggleEditOffline | app.selection.file<br/>!app.navigation.isTrashcan<br/>canToggleFileLock |
| canEditFolder | app.selection.folder.canUpdate<br/>!app.navigation.isTrashcan |
| app.toolbar.favorite.canAdd | app.selection.notEmpty<br/>app.selection.canAddFavorite<br/>!app.navigation.isTrashcan<br/>!app.navigation.isRecentFiles<br/>!app.navigation.isSharedFiles<br/>!app.navigation.isSearchResults<br/>!app.navigation.isFavorites |
| app.toolbar.favorite.canRemove | app.selection.notEmpty<br/>app.selection.canRemoveFavorite<br/>!app.navigation.isTrashcan<br/>!app.navigation.isRecentFiles<br/>!app.navigation.isSharedFiles<br/>!app.navigation.isSearchResults<br/>!app.navigation.isFavorites |
| canCopyNode | app.selection.notEmpty<br/>!app.navigation.isTrashcan<br/>!app.navigation.isLibraries |
| canManageFileVersions | app.selection.file<br/>!app.navigation.isTrashcan<br/>!app.selection.file.isLocked |
| canManagePermissions | app.selection.first.canUpdate<br/>!app.navigation.isTrashcan<br/>!isSmartFolder<br/>!isMultiSelection |
| rules.canManageFolderRules | rules.isFolderRulesEnabled<br/>app.selection.folder.canUpdate<br/>!app.navigation.isTrashcan<br/>app.selection.folder<br/>!app.navigation.isFavorites<br/>!isSmartFolder |
| app.navigation.folder.canCreate | app.isContentServiceEnabled<br/>app.navigation.folder.canCreate |
| app.navigation.folder.canUpload | app.isContentServiceEnabled<br/>app.navigation.folder.canCreate |
| app.isUploadSupported | app.isContentServiceEnabled<br/>app.navigation.folder.canCreate |
| canToggleSharedLink | app.selection.file<br/>canToggleSharedLink |
| canToggleJoinLibrary | app.selection.library<br/>!app.selection.hasLibraryRole<br/>canToggleJoinLibrary |
| canShowExpand | !app.navigation.isLibraries<br/>!app.navigation.isDetails |
| canInfoPreview | app.navigation.isSearchResults<br/>!isMultiSelection<br/>!app.selection.folder<br/>!app.navigation.isPreview |
| app.selection.canDelete | !app.navigation.isTrashcan<br/>!app.navigation.isLibraries<br/>app.selection.notEmpty<br/>app.selection.canDelete |
In addition to the above-mentioned migrations, the following set of rules were removed from the ACA codebase, since they were not being used by the application. If you still need access to these rules however, the original implementation
can be found in [legacy-rules.md](./legacy-rules.md). However, do note that since these rules have been removed from the ACA codebase, no support will be provided for them in the future. We advise that you should only use the provided rules as reference,
and migrate your current implementation to use a different approach instead.
| Rule |
|-----------------------------------|
| app.selection.file.canLock |
| app.selection.file.canUnlock |
| app.selection.file.isLockOwner |
| app.selection.file.canShare |
| app.selection.file.isShared |
| app.selection.canUnshare |
| app.selection.isPrivateLibrary |
| app.selection.hasNoLibraryRole |
| app.navigation.isLibraryFiles |
| app.navigation.isPersonalFiles |
| app.navigation.isSharedPreview |
| app.navigation.isFavoritesPreview |
| app.navigation.isSharedFileViewer |
| repository.isQuickShareEnabled |
| user.isAdmin |

View File

@ -0,0 +1,78 @@
---
Title: Rules List
---
# Rules List
The following is a comprehensive list of all rules/evaluators available in the Alfresco content application,
under different modules. You may use these existing rules, or create your own following the
[rules](./rules.md) guide when building your extensions.
### AOS Extension plugin
Rules related to the Alfresco Office Services plugin
| Ver. | Key | Description |
|-------|-----------------------|-------------------------------------------------|
| 2.9.0 | aos.canOpenWithOffice | Checks if the file can be opened with MS Office |
### Folder Rules plugin
Rules related to the folder rules plugin
| Ver. | Key | Description |
|-------|----------------------------|-----------------------------------------------------|
| 5.1.1 | rules.isFolderRulesEnabled | Checks if the folder rules plugin is enabled or not |
### Content Services plugin
#### Application Rules/Evaluators
Application related evaluators which can be used to check various different aspects of the users interaction with the application
| Ver. | Key | Description |
|--------|-------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| 1.7.0 | app.selection.canDelete | User has permission to delete selected node(s). |
| 1.7.0 | app.selection.canDownload | User can download selected node(s). |
| 1.7.0 | app.selection.notEmpty | At least one node is selected. |
| 1.7.0 | app.selection.canAddFavorite | User can add selected node(s) to favorites. |
| 1.7.0 | app.selection.canRemoveFavorite | User can remove selected node(s) from favorites. |
| 1.7.0 | app.selection.first.canUpdate | User has permission to update selected node(s). |
| 1.7.0 | app.selection.file | A single File node is selected. |
| 1.7.0 | app.selection.file.isLocked | File is locked for editing. |
| 1.7.0 | app.selection.file.canUploadVersion | User can update file version. |
| 1.7.0 | app.selection.library | A single Library node is selected. |
| 1.7.0 | app.selection.hasLibraryRole | The selected Library node has a role property. |
| 1.7.0 | app.selection.folder | A single Folder node is selected. |
| 1.7.0 | app.selection.folder.canUpdate | User has permissions to update the selected folder. |
| 1.8.0 | canToggleJoinLibrary | Checks if user can perform "Join" or "Cancel Join Request" on a library. |
| 1.8.0 | canToggleSharedLink | Checks if user can toggle shared link mode. |
| 1.8.0 | canToggleFavorite | Checks whether the user can add/remove the selected file from favorites |
| 1.9.0 | app.canShowLogout | Whether logout action should be present or not. |
| 1.12.0 | isLibraryManager | Checks if user is library manager. |
| 2.3.0 | canEditAspects | Checks whether the user can change the aspects of the selected file |
| 2.10.0 | app.isContentServiceEnabled | Checks if the content services plugin is enabled or not |
| 4.4.0 | app.areTagsEnabled | Checks if the tags module is enabled or not |
| 4.4.0 | app.areCategoriesEnabled | Checks if the categories module is enabled or not |
| 5.1.1 | canToggleFileLock | Checks whether the user can lock/unlock the selected file |
| 5.1.1 | isSmartFolder | Checks if the selected folder has the 'smf:customConfigSmartFolder' or the 'smf:systemConfigSmartFolder' aspect or not |
| 5.1.1 | isMultiSelection | Checks if the user has selected multiple files |
#### Navigation Rules/Evaluators
Navigation related rules/evaluators which can be used to determine if the user is on a specific location within the application
or not.
| Version | Key | Description |
|---------|---------------------------------|------------------------------------------------------------------|
| 1.7.0 | app.navigation.folder.canCreate | User can create content in the currently opened folder. |
| 1.7.0 | app.navigation.isTrashcan | User is using the **Trashcan** page. |
| 1.7.0 | app.navigation.isLibraries | User is using a **Libraries** or **Library Search Result** page. |
| 1.7.0 | app.navigation.isSharedFiles | User is using the **Shared Files** page. |
| 1.7.0 | app.navigation.isFavorites | User is using the **Favorites** page. |
| 1.7.0 | app.navigation.isRecentFiles | User is using the **Recent Files** page. |
| 1.7.0 | app.navigation.isSearchResults | User is using the **Search Results** page. |
| 1.7.0 | app.navigation.isPreview | Current page is **Preview**. |
| 5.1.1 | app.navigation.isDetails | User is currently on the **Folder Details** page. |

View File

@ -195,84 +195,11 @@ You can now declare a toolbar button action that is based on the rule above.
The button will be visible only when the linked rule evaluates to `true`.
## Application Evaluators
| Ver. | Key | Description |
| ----- | ----------------------------------- | ------------------------------------------------------------------------ |
| 1.7.0 | app.selection.canDelete | User has permission to delete selected node(s). |
| 1.7.0 | app.selection.canDownload | User can download selected node(s). |
| 1.7.0 | app.selection.notEmpty | At least one node is selected. |
| 1.7.0 | app.selection.canUnshare | User is able to remove selected node(s) from public sharing. |
| 1.7.0 | app.selection.canAddFavorite | User can add selected node(s) to favorites. |
| 1.7.0 | app.selection.canRemoveFavorite | User can remove selected node(s) from favorites. |
| 1.7.0 | app.selection.first.canUpdate | User has permission to update selected node(s). |
| 1.7.0 | app.selection.file | A single File node is selected. |
| 1.7.0 | app.selection.file.canShare | User is able to share the selected file. |
| 1.7.0 | app.selection.file.isShared | A shared node is selected. |
| 1.7.0 | app.selection.file.isLocked | File is locked for editing. |
| 1.7.0 | app.selection.file.isLockOwner | File is locked and current user is the lock owner. |
| 1.7.0 | app.selection.file.canUploadVersion | User can update file version. |
| 1.7.0 | app.selection.library | A single Library node is selected. |
| 1.7.0 | app.selection.isPrivateLibrary | A private Library node is selected. |
| 1.7.0 | app.selection.hasLibraryRole | The selected Library node has a role property. |
| 1.7.0 | app.selection.hasNoLibraryRole | The selected Library node has no role property. |
| 1.7.0 | app.selection.folder | A single Folder node is selected. |
| 1.7.0 | app.selection.folder.canUpdate | User has permissions to update the selected folder. |
| 1.7.0 | app.selection.folder.canUpdate | User has permissions to update the selected folder. |
| 1.7.0 | app.selection.file.canLock | User has permissions to lock file. |
| 1.7.0 | app.selection.file.canUnlock | User has permissions to unlock file. |
| 1.7.0 | repository.isQuickShareEnabled | Whether the quick share repository option is enabled or not. |
| 1.8.0 | canCopyNode | Checks if user can copy selected node. |
| 1.8.0 | canToggleJoinLibrary | Checks if user can perform "Join" or "Cancel Join Request" on a library. |
| 1.8.0 | canEditFolder | Checks if user can edit the selected folder. |
| 1.8.0 | isTrashcanItemSelected | Checks if user has trashcan item selected. |
| 1.8.0 | canViewFile | Checks if user can view the file. |
| 1.8.0 | canLeaveLibrary | Checks if user can **Leave** selected library. |
| 1.8.0 | canToggleSharedLink | Checks if user can toggle shared link mode. |
| 1.8.0 | canShowInfoDrawer | Checks if user can show **Info Drawer** for the selected node. |
| 1.8.0 | canManageFileVersions | Checks if user can manage file versions for the selected node. |
| 1.8.0 | canManagePermissions | Checks if user can manage permissions for the selected node. |
| 1.8.0 | canToggleEditOffline | Checks if user can toggle **Edit Offline** mode for selected node. |
| 1.8.0 | user.isAdmin | Checks if user is admin. |
| 1.9.0 | app.canShowLogout | Whether logout action should be present or not. |
| 1.12.0 | app.isLibraryManager | Checks if user is library manager. |
## Navigation Evaluators
The application exposes a set of navigation-related evaluators to help developers restrict or enable certain actions based on the route or page displayed.
The negated evaluators are provided just to simplify development, and to avoid having complex rule trees just to negate the rules,
for example mixing `core.every` and `core.not`.
**Tip:** You can also negate any rule by utilizing a `!` prefix:
`!app.navigation.isTrashcan` is the opposite of the `app.navigation.isTrashcan`.
| Version | Key | Description |
|---------|-----------------------------------|------------------------------------------------------------------|
| 1.7.0 | app.navigation.folder.canCreate | User can create content in the currently opened folder. |
| 1.7.0 | app.navigation.folder.canUpload | User can upload content to the currently opened folder. |
| 1.7.0 | app.navigation.isTrashcan | User is using the **Trashcan** page. |
| 1.7.0 | app.navigation.isNotTrashcan | Current page is not a **Trashcan**. |
| 1.7.0 | app.navigation.isLibraries | User is using a **Libraries** or **Library Search Result** page. |
| 1.7.0 | app.navigation.isNotLibraries | Current page is not a **Libraries** page. |
| 1.7.0 | app.navigation.isSharedFiles | User is using the **Shared Files** page. |
| 1.7.0 | app.navigation.isNotSharedFiles | Current page is not **Shared Files**. |
| 1.7.0 | app.navigation.isFavorites | User is using the **Favorites** page. |
| 1.7.0 | app.navigation.isNotFavorites | Current page is not **Favorites**. |
| 1.7.0 | app.navigation.isRecentFiles | User is using the **Recent Files** page. |
| 1.7.0 | app.navigation.isNotRecentFiles | Current page is not **Recent Files**. |
| 1.7.0 | app.navigation.isSearchResults | User is using the **Search Results** page. |
| 1.7.0 | app.navigation.isNotSearchResults | Current page is not the **Search Results**. |
| 1.7.0 | app.navigation.isSharedPreview | Current page is preview **Shared Files**. |
| 1.7.0 | app.navigation.isFavoritesPreview | Current page is preview **Favorites**. |
| 1.7.0 | app.navigation.isSharedFileViewer | Current page is shared file preview page. |
| 1.7.0 | app.navigation.isPreview | Current page is **Preview**. |
| 1.7.0 | app.navigation.isPersonalFiles | Current page is **Personal Files**. |
| 1.7.0 | app.navigation.isLibraryFiles | Current page is **Library Files**. |
| 5.3.0 | app.navigation.isNotDetails | Current page is not **Details**. |
**Tip:** See the [Registration](./registration) section for more details
on how to register your own entries to be re-used at runtime.
**Tips:**
1. You can also negate any rule by utilizing a `!` prefix: `!app.navigation.isTrashcan` is the opposite of the `app.navigation.isTrashcan`.
2. See the [Registration](./registration) section for more details on how to register your own entries to be re-used at runtime.
3. See [Rules List](./rules-list.md) for a full list of rules available with the Alfresco Content Application
### Example

View File

@ -168,8 +168,19 @@
"type": "string"
},
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
"description": "Rule/Set of rules to evaluate the visibility state",
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minLength": 1
}
]
}
}
}
@ -250,8 +261,19 @@
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
"description": "Rule/Set of rules to evaluate the visibility state",
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minLength": 1
}
]
}
}
}
@ -294,8 +316,19 @@
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
"description": "Rule/Set of rules to evaluate the visibility state",
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minLength": 1
}
]
}
}
}
@ -334,8 +367,19 @@
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
"description": "Rule/Set of rules to evaluate the visibility state",
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minLength": 1
}
]
}
}
}
@ -649,8 +693,19 @@
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
"description": "Rule/Set of rules to evaluate the visibility state",
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minLength": 1
}
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,9 @@
"id": "app.toolbar.rules.separator",
"type": "separator",
"rules": {
"visible": "app.selection.folder"
"visible": [
"app.selection.folder"
]
}
},
{
@ -24,7 +26,9 @@
"description": "ACA_FOLDER_RULES.MENU.CREATE_RULES_DESC",
"icon": "add",
"rules": {
"visible": "rules.canCreateFolderRule"
"visible": [
"rules.canCreateFolderRule"
]
}
},
{
@ -33,7 +37,9 @@
"description": "ACA_FOLDER_RULES.MENU.LINK_RULES_DESC",
"icon": "link",
"rules": {
"visible": "rules.canLinkFolderRule"
"visible": [
"rules.canLinkFolderRule"
]
}
}
]
@ -45,7 +51,9 @@
"id": "app.toolbar.rules.separator",
"type": "separator",
"rules": {
"visible": "app.selection.folder"
"visible": [
"app.selection.folder"
]
}
},
{
@ -54,7 +62,9 @@
"description": "ACA_FOLDER_RULES.MENU.CREATE_RULES_DESC",
"icon": "add",
"rules": {
"visible": "rules.canCreateFolderRule"
"visible": [
"rules.canCreateFolderRule"
]
}
},
{
@ -63,7 +73,9 @@
"description": "ACA_FOLDER_RULES.MENU.LINK_RULES_DESC",
"icon": "link",
"rules": {
"visible": "rules.canLinkFolderRule"
"visible": [
"rules.canLinkFolderRule"
]
}
}
]

View File

@ -77,7 +77,7 @@ export class AcaFolderRulesModule {
translation.addTranslationFolder('folder-rules', 'assets/folder-rules');
extensions.setEvaluators({
'rules.canManageFolderRules': rules.canManageFolderRules
'rules.isFolderRulesEnabled': rules.isFolderRulesEnabled
});
}
}

View File

@ -22,9 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AcaRuleContext } from '@alfresco/aca-shared/rules';
import { canManageFolderRules, isFolderRulesEnabled } from './folder-rules.rules';
import { NodeEntry } from '@alfresco/js-api';
import { isFolderRulesEnabled } from './folder-rules.rules';
describe('Folder Rules Visibility Rules', () => {
describe('isFolderRulesEnabled', () => {
@ -50,44 +48,4 @@ describe('Folder Rules Visibility Rules', () => {
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);
});
it('should not allow creating a rule if the selected node is a smart folder', () => {
context.selection.first = { entry: { aspectNames: ['smf:customConfigSmartFolder'], isFolder: true } } as NodeEntry;
const result = canManageFolderRules(context);
expect(result).toBe(false);
});
});
});

View File

@ -22,10 +22,6 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AcaRuleContext, canEditFolder, hasFolderSelected, isNotFavorites, isSmartFolder } from '@alfresco/aca-shared/rules';
import { AcaRuleContext } 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) && !isSmartFolder(context);
export const canManageFolderRules = (context: AcaRuleContext): boolean => isFolderRulesAllowed(context);

View File

@ -29,7 +29,9 @@
"click": "aos.openWith.office"
},
"rules": {
"visible": "aos.canOpenWithOffice"
"visible": [
"aos.canOpenWithOffice"
]
}
}
]
@ -45,7 +47,9 @@
"click": "aos.openWith.office"
},
"rules": {
"visible": "aos.canOpenWithOffice"
"visible": [
"aos.canOpenWithOffice"
]
}
}
],
@ -63,7 +67,9 @@
"click": "aos.openWith.office"
},
"rules": {
"visible": "aos.canOpenWithOffice"
"visible": [
"aos.canOpenWithOffice"
]
}
}
]

View File

@ -163,74 +163,42 @@ export class ContentServiceExtensionModule {
});
extensions.setEvaluators({
canCopyNode: rules.canCopyNode,
canToggleJoinLibrary: rules.canToggleJoinLibrary,
canEditFolder: rules.canEditFolder,
isTrashcanItemSelected: rules.isTrashcanItemSelected,
canViewFile: rules.canViewFile,
canLeaveLibrary: rules.canLeaveLibrary,
canToggleSharedLink: rules.canToggleSharedLink,
canShowInfoDrawer: rules.canShowInfoDrawer,
canManageFileVersions: rules.canManageFileVersions,
canManagePermissions: rules.canManagePermissions,
canToggleEditOffline: rules.canToggleEditOffline,
canToggleFileLock: rules.canToggleFileLock,
canToggleFavorite: rules.canToggleFavorite,
isLibraryManager: rules.isLibraryManager,
canEditAspects: rules.canEditAspects,
canShowExpand: rules.canShowExpand,
canInfoPreview: rules.canInfoPreview,
isSmartFolder: rules.isSmartFolder,
isMultiSelection: rules.isMultiselection,
'app.selection.canDelete': rules.canDeleteSelection,
'app.selection.file.canUnlock': rules.canUnlockFile,
'app.selection.file.canLock': rules.canLockFile,
'app.selection.canDownload': rules.canDownloadSelection,
'app.selection.notEmpty': rules.hasSelection,
'app.selection.canUnshare': rules.canUnshareNodes,
'app.selection.canAddFavorite': rules.canAddFavorite,
'app.selection.canRemoveFavorite': rules.canRemoveFavorite,
'app.selection.first.canUpdate': rules.canUpdateSelectedNode,
'app.selection.file': rules.hasFileSelected,
'app.selection.file.canShare': rules.canShareFile,
'app.selection.file.isShared': rules.isShared,
'app.selection.file.isLocked': rules.hasLockedFiles,
'app.selection.file.isLockOwner': rules.isUserWriteLockOwner,
'app.selection.file.canUploadVersion': rules.canUploadVersion,
'app.selection.library': rules.hasLibrarySelected,
'app.selection.isPrivateLibrary': rules.isPrivateLibrary,
'app.selection.hasLibraryRole': rules.hasLibraryRole,
'app.selection.hasNoLibraryRole': rules.hasNoLibraryRole,
'app.selection.folder': rules.hasFolderSelected,
'app.selection.folder.canUpdate': rules.canUpdateSelectedFolder,
'app.selection.displayedKnowledgeRetrievalButton': rules.canDisplayKnowledgeRetrievalButton,
'app.navigation.folder.canCreate': rules.canCreateFolder,
'app.navigation.folder.canUpload': rules.canUpload,
'app.navigation.isTrashcan': rules.isTrashcan,
'app.navigation.isNotTrashcan': rules.isNotTrashcan,
'app.navigation.isLibraries': rules.isLibraries,
'app.navigation.isLibraryFiles': rules.isLibraryFiles,
'app.navigation.isPersonalFiles': rules.isPersonalFiles,
'app.navigation.isNotLibraries': rules.isNotLibraries,
'app.navigation.isSharedFiles': rules.isSharedFiles,
'app.navigation.isNotSharedFiles': rules.isNotSharedFiles,
'app.navigation.isFavorites': rules.isFavorites,
'app.navigation.isNotFavorites': rules.isNotFavorites,
'app.navigation.isRecentFiles': rules.isRecentFiles,
'app.navigation.isNotRecentFiles': rules.isNotRecentFiles,
'app.navigation.isSearchResults': rules.isSearchResults,
'app.navigation.isNotSearchResults': rules.isNotSearchResults,
'app.navigation.isPreview': rules.isPreview,
'app.navigation.isSharedPreview': rules.isSharedPreview,
'app.navigation.isFavoritesPreview': rules.isFavoritesPreview,
'app.navigation.isSharedFileViewer': rules.isSharedFileViewer,
'app.navigation.isNotDetails': rules.isNotDetails,
'app.navigation.isDetails': rules.isDetails,
'repository.isQuickShareEnabled': rules.hasQuickShareEnabled,
'user.isAdmin': rules.isAdmin,
'app.canShowLogout': rules.canShowLogout,
'app.isContentServiceEnabled': rules.isContentServiceEnabled,
'app.isUploadSupported': rules.isUploadSupported,
'app.canCreateLibrary': rules.canCreateLibrary,
'app.areTagsEnabled': rules.areTagsEnabled,
'app.areCategoriesEnabled': rules.areCategoriesEnabled
});

View File

@ -23,9 +23,10 @@
*/
import * as app from './app.rules';
import { getFileExtension } from './app.rules';
import { TestRuleContext } from './test-rule-context';
import { NodeEntry, RepositoryInfo, StatusInfo } from '@alfresco/js-api';
import { getFileExtension } from './app.rules';
import { ProfileState } from '@alfresco/adf-extensions';
import { AppConfigService } from '@alfresco/adf-core';
describe('app.evaluators', () => {
@ -105,20 +106,6 @@ describe('app.evaluators', () => {
});
});
describe('canShowExpand', () => {
it('should return false when isLibraries returns true', () => {
context.navigation.url = '/libraries';
expect(app.canShowExpand(context)).toBe(false);
});
it('should return false when isDetails returns true', () => {
context.navigation.url = '/details';
expect(app.canShowExpand(context)).toBe(false);
});
});
describe('hasLockedFiles', () => {
it('should return [false] if selection not present', () => {
context = {} as any;
@ -455,34 +442,6 @@ describe('app.evaluators', () => {
});
});
describe('canManagePermissions', () => {
it('should return false if user cannot update the selected node', () => {
context.permissions.check = spyOn(context.permissions, 'check').and.returnValue(false);
expect(app.canManagePermissions(context)).toBe(false);
});
it('should return false if the context is trashcan', () => {
context.navigation = { url: '/trashcan' };
expect(app.canManagePermissions(context)).toBe(false);
});
it('should return false if many nodes are selected', () => {
context.selection.count = 2;
expect(app.canManagePermissions(context)).toBe(false);
});
it('should return false if the selected node is a smart folder', () => {
context.selection.first = { entry: { aspectNames: ['smf:customConfigSmartFolder'], isFolder: true } } as NodeEntry;
expect(app.canManagePermissions(context)).toBe(false);
});
it('should return true if user can update the selected node and it is not a trashcan nor smart folder nor multiselect', () => {
expect(app.canManagePermissions(context)).toBe(true);
});
});
describe('areTagsEnabled', () => {
it('should call context.appConfig.get with correct parameters', () => {
context.appConfig = { get: jasmine.createSpy() } as any;
@ -691,34 +650,6 @@ describe('app.evaluators', () => {
});
});
describe('canCopyNode', () => {
it('should return false when nothing is selected', () => {
context.selection.isEmpty = true;
expect(app.canCopyNode(context)).toBeFalse();
});
it('should return false when selection exists and user is in trashcan', () => {
context.selection.isEmpty = false;
context.navigation.url = '/trashcan/test';
expect(app.canCopyNode(context)).toBeFalse();
});
it('should return false when selection exists and user is in library', () => {
context.selection.isEmpty = false;
context.navigation.url = '/test/libraries';
expect(app.canCopyNode(context)).toBeFalse();
context.navigation.url = '/search-libraries/test';
expect(app.canCopyNode(context)).toBeFalse();
});
it('should return true when selection exists and user is outside library and trashcan', () => {
context.selection.isEmpty = false;
context.navigation.url = '/personal-files';
expect(app.canCopyNode(context)).toBeTrue();
});
});
describe('canAddFavorite', () => {
it('should return false when nothing is selected', () => {
context.selection.isEmpty = true;
@ -966,35 +897,6 @@ describe('app.evaluators', () => {
});
});
describe('canUnshareNodes', () => {
it('should return false when selection is empty', () => {
context.selection.isEmpty = true;
expect(app.canUnshareNodes(context)).toBeFalse();
});
it('should return false when permission check fails', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{} as any];
context.permissions = { check: () => false };
expect(app.canUnshareNodes(context)).toBeFalse();
});
it('should return true when permission requirements are met', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{} as any];
context.permissions = { check: () => true };
expect(app.canUnshareNodes(context)).toBeTrue();
});
it('should verify if user have delete permission on selected node', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ allowableOperationsOnTarget: ['delete'] } as any];
spyOn(context.permissions, 'check');
app.canUnshareNodes(context);
expect(context.permissions.check).toHaveBeenCalledWith(context.selection.nodes, ['delete'], { target: 'allowableOperationsOnTarget' });
});
});
describe('hasSelection', () => {
it('should return false when nothing is selected', () => {
context.selection.isEmpty = true;
@ -1052,70 +954,6 @@ describe('app.evaluators', () => {
});
});
describe('canCreateLibrary', () => {
it('should return false when content service is disabled', () => {
context.appConfig = { get: () => false } as any;
expect(app.canCreateLibrary(context)).toBeFalse();
});
it('should return false when user is outside libraries', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/favorite/test';
expect(app.canCreateLibrary(context)).toBeFalse();
});
it('should return true when content service is enabled and user is in libraries', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/test/libraries';
expect(app.canCreateLibrary(context)).toBeTrue();
});
});
describe('canUpload', () => {
it('should return false when content service is disabled', () => {
context.appConfig = { get: () => false } as any;
expect(app.canUpload(context)).toBeFalse();
});
it('should return false when user is outside personal files or libraries', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/favorite/test';
expect(app.canUpload(context)).toBeFalse();
});
it('should return false when current folder does not exist', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/personal-files/test';
context.navigation.currentFolder = null;
expect(app.canUpload(context)).toBeFalse();
});
it('should return false when permission check fails', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/personal-files/test';
context.navigation.currentFolder = {} as any;
context.permissions = { check: () => false };
expect(app.canUpload(context)).toBeFalse();
});
it('should return true when permission requirements are met', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/personal-files/test';
context.navigation.currentFolder = {} as any;
context.permissions = { check: () => true };
expect(app.canUpload(context)).toBeTrue();
});
it('should verify if user has create permission on current folder', () => {
context.appConfig = { get: () => true } as any;
context.navigation.url = '/personal-files/test';
context.navigation.currentFolder = { allowableOperations: ['create'] } as any;
spyOn(context.permissions, 'check');
app.canCreateFolder(context);
expect(context.permissions.check).toHaveBeenCalledWith(context.navigation.currentFolder, ['create']);
});
});
describe('hasFileSelected', () => {
it('should return false when no file is selected', () => {
context.selection.file = null;
@ -1176,18 +1014,6 @@ describe('app.evaluators', () => {
});
});
describe('hasNoLibraryRole', () => {
it('should return false when library has a role', () => {
context.selection.library = { entry: { role: 'test' } } as any;
expect(app.hasNoLibraryRole(context)).toBeFalse();
});
it('should return true when library has no role', () => {
context.selection.library = { entry: { role: '' } } as any;
expect(app.hasNoLibraryRole(context)).toBeTrue();
});
});
describe('isMultiselection', () => {
it('should return false when there is no or single selection', () => {
context.selection.isEmpty = true;
@ -1322,58 +1148,35 @@ describe('app.evaluators', () => {
});
});
describe('isTrashcanItemSelected', () => {
it('should return false when user is not in trashcan', () => {
context.navigation.url = '/personal-files/test';
expect(app.isTrashcanItemSelected(context)).toBeFalse();
describe('canToggleFileLock', () => {
beforeEach(() => {
context.profile = {} as ProfileState;
});
it('should return false when nothing is selected', () => {
context.navigation.url = '/trashcan/test';
context.selection.isEmpty = true;
expect(app.isTrashcanItemSelected(context)).toBeFalse();
it('should return false when permission requirements are not met, regardless of file lock', () => {
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK', 'cm:lockOwner': { id: 'test' } } } } as NodeEntry;
context.permissions = { check: () => false };
expect(app.canToggleFileLock(context)).toBeFalse();
});
it('should return true when user is in trashcan and node is selected', () => {
context.navigation.url = '/trashcan/test';
context.selection.isEmpty = false;
expect(app.isTrashcanItemSelected(context)).toBeTrue();
});
});
describe('canViewFile', () => {
it('should return false when user is in trashcan', () => {
context.navigation.url = '/trashcan/test';
expect(app.canViewFile(context)).toBeFalse();
it('should return true when file has no lock and permission requirements are met', () => {
context.selection.file = { entry: { properties: {} } } as NodeEntry;
context.permissions = { check: () => true };
expect(app.canToggleFileLock(context)).toBeTrue();
});
it('should return false when nothing is selected', () => {
context.navigation.url = '/personal-files/test';
context.selection.file = null;
expect(app.canViewFile(context)).toBeFalse();
it('should return true when file is locked and permission requirements are met', () => {
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK', 'cm:lockOwner': { id: 'test' } } } } as NodeEntry;
context.profile.id = 'test1';
context.permissions = { check: () => true };
expect(app.canToggleFileLock(context)).toBeTrue();
});
it('should return true when user is not in trashcan and file is selected', () => {
context.navigation.url = '/personal-files/test';
context.selection.file = {} as any;
expect(app.canViewFile(context)).toBeTrue();
});
});
describe('canLeaveLibrary', () => {
it('should return false when no library is selected', () => {
context.selection.library = null;
expect(app.canLeaveLibrary(context)).toBeFalse();
});
it('should return false when user does not have library role', () => {
context.selection.library = { entry: { role: null } } as any;
expect(app.canLeaveLibrary(context)).toBeFalse();
});
it('should return true when user has a library role', () => {
context.selection.library = { entry: { role: 'test' } } as any;
expect(app.canLeaveLibrary(context)).toBeTrue();
it('should return true when file is locked and user is the owner of the lock', () => {
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK', 'cm:lockOwner': { id: 'test1' } } } } as NodeEntry;
context.profile.id = 'test1';
context.permissions = { check: () => false };
expect(app.canToggleFileLock(context)).toBeTrue();
});
});
@ -1406,136 +1209,6 @@ describe('app.evaluators', () => {
});
});
describe('canShowInfoDrawer', () => {
it('should return false when nothing is selected', () => {
context.selection.isEmpty = true;
expect(app.canShowInfoDrawer(context)).toBeFalse();
});
it('should return false when user is in libraries or trashcan or details', () => {
context.selection.isEmpty = false;
context.navigation.url = '/trashcan/test';
expect(app.canShowInfoDrawer(context)).toBeFalse();
context.navigation.url = '/test/libraries';
expect(app.canShowInfoDrawer(context)).toBeFalse();
context.navigation.url = '/test/details';
expect(app.canShowInfoDrawer(context)).toBeFalse();
});
it('should return true when selection exists and user is not in trashcan, libraries or details', () => {
context.navigation.url = '/personal-files/test';
context.selection.isEmpty = false;
expect(app.canShowInfoDrawer(context)).toBeTrue();
});
});
describe('canManageFileVersions', () => {
it('should return false when no file is selected', () => {
context.selection.file = null;
expect(app.canManageFileVersions(context)).toBeFalse();
});
it('should return false when user is in trashcan', () => {
context.selection.file = {} as any;
context.navigation.url = '/trashcan/test';
expect(app.canManageFileVersions(context)).toBeFalse();
});
it('should return false when locked file is selected', () => {
context.selection.file = {} as any;
context.selection.nodes = [{ entry: { isFile: true, isLocked: true } } as any];
context.navigation.url = '/personal-files/test';
expect(app.canManageFileVersions(context)).toBeFalse();
});
it('should return true when non-locked file is selected', () => {
context.selection.file = {} as any;
context.selection.nodes = [{ entry: { isFile: true, isLocked: false } } as any];
context.navigation.url = '/personal-files/test';
expect(app.canManageFileVersions(context)).toBeTrue();
});
});
describe('canToggleEditOffline', () => {
beforeEach(() => {
context.profile = {} as any;
});
it('should return false when no file is selected', () => {
context.selection.file = null;
expect(app.canToggleEditOffline(context)).toBeFalse();
});
it('should return false when user is in trashcan', () => {
context.selection.file = {} as any;
context.navigation.url = '/trashcan/test';
expect(app.canToggleEditOffline(context)).toBeFalse();
});
it('should return false when user cannot lock or unlock files', () => {
context.navigation.url = '/personal-files/test';
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK', 'cm:lockOwner': { id: 'test' } } } } as any;
context.profile.id = 'test1';
context.permissions = { check: () => false };
expect(app.canToggleEditOffline(context)).toBeFalse();
});
it('should return true when user can lock file', () => {
context.navigation.url = '/personal-files/test';
context.selection.file = {} as any;
context.permissions = { check: () => true };
expect(app.canToggleEditOffline(context)).toBeTrue();
});
it('should return true when user can unlock file', () => {
context.navigation.url = '/personal-files/test';
context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK', 'cm:lockOwner': { id: 'test1' } } } } as any;
context.profile.id = 'test1';
expect(app.canToggleEditOffline(context)).toBeTrue();
});
});
describe('canInfoPreview', () => {
it('should return false when user is not is search results page', () => {
context.navigation.url = '/trashcan/test';
expect(app.canInfoPreview(context)).toBeFalse();
});
it('should return false when multiple nodes are selected', () => {
context.navigation.url = '/search/test';
context.selection.isEmpty = false;
context.selection.count = 5;
expect(app.canInfoPreview(context)).toBeFalse();
});
it('should return false when folder is selected', () => {
context.navigation.url = '/search/test';
context.selection.isEmpty = false;
context.selection.count = 1;
context.selection.folder = {} as any;
expect(app.canInfoPreview(context)).toBeFalse();
});
it('should return false when user is in preview', () => {
context.navigation.url = '/preview/test';
context.selection.isEmpty = false;
context.selection.count = 1;
context.selection.folder = null;
expect(app.canInfoPreview(context)).toBeFalse();
});
it('should return true when user is in search results page and single file is selected', () => {
context.navigation.url = '/search/test';
context.selection.isEmpty = false;
context.selection.count = 1;
context.selection.folder = null;
context.selection.file = {} as any;
expect(app.canInfoPreview(context)).toBeTrue();
});
});
describe('isSmartFolder', () => {
it('should return false when there is no selection', () => {
context.selection.isEmpty = true;

View File

@ -94,35 +94,15 @@ export const isContentServiceEnabled = (context: AcaRuleContext): boolean => {
return flag === true || flag === 'true';
};
/**
* Checks if upload action is supported for the given context
* JSON ref: `app.isUploadSupported`
*
* @param context Rule execution context
*/
export const isUploadSupported = (context: AcaRuleContext): boolean =>
[isContentServiceEnabled(context), navigation.isPersonalFiles(context) || navigation.isLibraryContent(context), canUpload(context)].every(Boolean);
/**
* Checks if user can copy selected node.
* JSON ref: `app.canCopyNode`
*
* @param context Rule execution context
*/
export const canCopyNode = (context: RuleContext): boolean =>
[hasSelection(context), navigation.isNotTrashcan(context), navigation.isNotLibraries(context)].every(Boolean);
/**
* Checks if user can mark selected nodes as **Favorite**.
* JSON ref: `app.selection.canAddFavorite`
*/
export function canAddFavorite(context: RuleContext): boolean {
if (hasSelection(context)) {
if (navigation.isFavorites(context) || navigation.isLibraries(context) || navigation.isTrashcan(context)) {
return false;
}
return context.selection.nodes.some((node) => !node.entry.isFavorite);
if (navigation.isFavorites(context) || navigation.isLibraries(context) || navigation.isTrashcan(context)) {
return false;
}
return false;
return context.selection.nodes.some((node) => !node.entry.isFavorite);
}
/**
@ -130,13 +110,10 @@ export function canAddFavorite(context: RuleContext): boolean {
* JSON ref: `app.selection.canRemoveFavorite`
*/
export function canRemoveFavorite(context: RuleContext): boolean {
if (hasSelection(context) && !navigation.isTrashcan(context)) {
if (navigation.isFavorites(context)) {
return true;
}
return context.selection.nodes.every((node) => node.entry.isFavorite);
if (navigation.isFavorites(context)) {
return true;
}
return false;
return context.selection.nodes.every((node) => node.entry.isFavorite);
}
/**
@ -144,15 +121,14 @@ export function canRemoveFavorite(context: RuleContext): boolean {
* JSON ref: `app.selection.file.canShare`
*/
export const canShareFile = (context: RuleContext): boolean =>
[context.selection.file, navigation.isNotTrashcan(context), repository.hasQuickShareEnabled(context), !isShared(context)].every(Boolean);
[context.selection.file, !navigation.isTrashcan(context), repository.hasQuickShareEnabled(context), !isShared(context)].every(Boolean);
/**
* Checks if user can perform "Join" or "Cancel Join Request" on a library.
* JSON ref: `canToggleJoinLibrary`
*/
export const canToggleJoinLibrary = (context: RuleContext): boolean =>
[hasLibrarySelected(context), !isPrivateLibrary(context), hasNoLibraryRole(context)].every(Boolean) ||
[hasLibrarySelected(context), isPrivateLibrary(context), hasNoLibraryRole(context), isAdmin(context)].every(Boolean);
!isPrivateLibrary(context) || [isPrivateLibrary(context), isAdmin(context)].every(Boolean);
/**
* Checks if user can edit the selected folder.
@ -160,7 +136,7 @@ export const canToggleJoinLibrary = (context: RuleContext): boolean =>
*
* @param context Rule execution context
*/
export const canEditFolder = (context: RuleContext): boolean => [canUpdateSelectedFolder(context), navigation.isNotTrashcan(context)].every(Boolean);
export const canEditFolder = (context: RuleContext): boolean => [canUpdateSelectedFolder(context), !navigation.isTrashcan(context)].every(Boolean);
/**
* Checks if the selected file is already shared.
@ -171,7 +147,7 @@ export function isShared(context: RuleContext): boolean {
return true;
}
if (navigation.isNotTrashcan(context) && hasSelection(context) && context.selection.file) {
if (!navigation.isTrashcan(context) && hasSelection(context) && context.selection.file) {
return !!context.selection.file.entry?.properties?.['qshare:sharedId'];
}
@ -183,43 +159,27 @@ export function isShared(context: RuleContext): boolean {
* JSON ref: `app.selection.canDelete`
*/
export function canDeleteSelection(context: RuleContext): boolean {
if (navigation.isNotTrashcan(context) && navigation.isNotLibraries(context) && hasSelection(context)) {
if (hasLockedFiles(context)) {
return false;
}
if (hasLockedFiles(context)) {
return false;
}
// temp workaround for Favorites api
if (navigation.isFavorites(context)) {
return true;
}
if (navigation.isPreview(context)) {
return context.permissions.check(context.selection.nodes, ['delete']);
}
// workaround for Shared Files
if (navigation.isSharedFiles(context)) {
return context.permissions.check(context.selection.nodes, ['delete'], {
target: 'allowableOperationsOnTarget'
});
}
// temp workaround for Favorites api
if (navigation.isFavorites(context)) {
return true;
}
if (navigation.isPreview(context)) {
return context.permissions.check(context.selection.nodes, ['delete']);
}
return false;
}
/**
* Checks if user can un-share selected nodes.
* JSON ref: `app.selection.canUnshare`
*/
export function canUnshareNodes(context: RuleContext): boolean {
if (hasSelection(context)) {
// workaround for Shared Files
if (navigation.isSharedFiles(context)) {
return context.permissions.check(context.selection.nodes, ['delete'], {
target: 'allowableOperationsOnTarget'
});
}
return false;
return context.permissions.check(context.selection.nodes, ['delete']);
}
/**
@ -233,7 +193,7 @@ export const hasSelection = (context: RuleContext): boolean => !context.selectio
* JSON ref: `app.navigation.folder.canCreate`
*/
export function canCreateFolder(context: AcaRuleContext): boolean {
if (isContentServiceEnabled(context) && (navigation.isPersonalFiles(context) || navigation.isLibraryContent(context))) {
if (navigation.isPersonalFiles(context) || navigation.isLibraryContent(context)) {
const { currentFolder } = context.navigation;
if (currentFolder) {
@ -243,32 +203,12 @@ export function canCreateFolder(context: AcaRuleContext): boolean {
return false;
}
/**
* Checks if user can create a Library
* JSON ref: `app.canCreateLibrary`
*
* @param context Rule execution context
*/
export const canCreateLibrary = (context: AcaRuleContext): boolean =>
[isContentServiceEnabled(context), navigation.isLibraries(context)].every(Boolean);
/**
* Checks if user can upload content to current folder.
* JSON ref: `app.navigation.folder.canUpload`
*/
export function canUpload(context: AcaRuleContext): boolean {
return canCreateFolder(context);
}
/**
* Checks if user can download selected nodes (either files or folders).
* JSON ref: `app.selection.canDownload`
*/
export function canDownloadSelection(context: RuleContext): boolean {
if (hasSelection(context) && navigation.isNotTrashcan(context)) {
return context.selection.nodes.every((node: any) => node.entry && (node.entry.isFile || node.entry.isFolder || !!node.entry.nodeId));
}
return false;
return context.selection.nodes.every((node: any) => node.entry && (node.entry.isFile || node.entry.isFolder || !!node.entry.nodeId));
}
/**
@ -300,12 +240,6 @@ export function hasLibraryRole(context: RuleContext): boolean {
return library ? !!library.entry?.role : false;
}
/**
* Checks if the selected library has no **role** property defined.
* JSON ref: `app.selection.hasNoLibraryRole`
*/
export const hasNoLibraryRole = (context: RuleContext): boolean => !hasLibraryRole(context);
/**
* Checks if user has selected a file.
* JSON ref: `app.selection.file`
@ -416,61 +350,18 @@ export function canUploadVersion(context: RuleContext): boolean {
return [
hasFileSelected(context),
navigation.isNotTrashcan(context),
!navigation.isTrashcan(context),
isWriteLocked(context) ? isUserWriteLockOwner(context) : canUpdateSelectedNode(context)
].every(Boolean);
}
/**
* Checks if user has trashcan item selected.
* JSON ref: `isTrashcanItemSelected`
*
* @param context Rule execution context
*/
export const isTrashcanItemSelected = (context: RuleContext): boolean => [navigation.isTrashcan(context), hasSelection(context)].every(Boolean);
/**
* Checks if user can view the file.
* JSON ref: `canViewFile`
*
* @param context Rule execution context
*/
export const canViewFile = (context: RuleContext): boolean => [hasFileSelected(context), navigation.isNotTrashcan(context)].every(Boolean);
/**
* Checks if user can **Leave** selected library.
* JSON ref: `canLeaveLibrary`
*
* @param context Rule execution context
*/
export const canLeaveLibrary = (context: RuleContext): boolean => [hasLibrarySelected(context), hasLibraryRole(context)].every(Boolean);
/**
* Checks if user can toggle shared link mode.
* JSON ref: `canToggleSharedLink`
*
* @param context Rule execution context
*/
export const canToggleSharedLink = (context: RuleContext): boolean =>
[hasFileSelected(context), [canShareFile(context), isShared(context)].some(Boolean)].every(Boolean);
/**
* Checks if user can show **Info Drawer** for the selected node.
* JSON ref: `canShowInfoDrawer`
*
* @param context Rule execution context
*/
export const canShowInfoDrawer = (context: RuleContext): boolean =>
[hasSelection(context), navigation.isNotLibraries(context), navigation.isNotTrashcan(context)].every(Boolean);
/**
* Checks if user can manage file versions for the selected node.
* JSON ref: `canManageFileVersions`
*
* @param context Rule execution context
*/
export const canManageFileVersions = (context: RuleContext): boolean =>
[hasFileSelected(context), navigation.isNotTrashcan(context), !hasLockedFiles(context)].every(Boolean);
export const canToggleSharedLink = (context: RuleContext): boolean => [canShareFile(context), isShared(context)].some(Boolean);
/**
* Checks if user can edit aspects for the selected node.
@ -483,29 +374,11 @@ export const canEditAspects = (context: RuleContext): boolean =>
!isMultiselection(context),
canUpdateSelectedNode(context),
!isWriteLocked(context),
navigation.isNotTrashcan(context),
!navigation.isTrashcan(context),
repository.isMajorVersionAvailable(context, '7')
].every(Boolean);
export const canShowExpand = (context: RuleContext): boolean => [!navigation.isLibraries(context), !navigation.isDetails(context)].every(Boolean);
/**
* Checks if user can manage permissions for the selected node.
* JSON ref: `canManagePermissions`
*
* @param context Rule execution context
*/
export const canManagePermissions = (context: RuleContext): boolean =>
[canUpdateSelectedNode(context), navigation.isNotTrashcan(context), !isSmartFolder(context), !isMultiselection(context)].every(Boolean);
/**
* Checks if user can toggle **Edit Offline** mode for selected node.
* JSON ref: `canToggleEditOffline`
*
* @param context Rule execution context
*/
export const canToggleEditOffline = (context: RuleContext): boolean =>
[hasFileSelected(context), navigation.isNotTrashcan(context), canLockFile(context) || canUnlockFile(context)].every(Boolean);
export const canToggleFileLock = (context: RuleContext): boolean => [canLockFile(context) || canUnlockFile(context)].some(Boolean);
/**
* @deprecated Uses workarounds for for recent files and search api issues.
@ -534,23 +407,7 @@ export const canShowLogout = (context: AcaRuleContext): boolean => !context.with
*
* @param context Rule execution context
*/
export const isLibraryManager = (context: RuleContext): boolean =>
hasLibrarySelected(context) && (context.selection.library?.entry.role === 'SiteManager' || isAdmin(context));
/**
* Checks if the preview button for search results can be showed
* JSON ref: `canInfoPreview`
*
* @param context Rule execution context
*/
export const canInfoPreview = (context: RuleContext): boolean => {
const isSearchResult = navigation.isSearchResults(context);
const isNotMultiselect = !isMultiselection(context);
const isFileSelected = !hasFolderSelected(context);
const isPreview = !navigation.isPreview(context);
return isSearchResult && isNotMultiselect && isFileSelected && isPreview;
};
export const isLibraryManager = (context: RuleContext): boolean => context.selection.library?.entry.role === 'SiteManager' || isAdmin(context);
/**
* Checks if the file can be opened with MS Office
@ -636,4 +493,4 @@ export const canDisplayKnowledgeRetrievalButton = (context: AcaRuleContext): boo
navigation.isSharedFiles(context) ||
navigation.isRecentFiles(context) ||
navigation.isFavorites(context) ||
((navigation.isSearchResults(context) || navigation.isLibraryContent(context)) && navigation.isNotLibraries(context)));
((navigation.isSearchResults(context) || navigation.isLibraryContent(context)) && !navigation.isLibraries(context)));

View File

@ -59,28 +59,6 @@ describe('navigation.evaluators', () => {
});
});
describe('isNotFavorites', () => {
it('should return [true] if url is not `/favorites`', () => {
const context: any = {
navigation: {
url: '/some/path'
}
};
expect(app.isNotFavorites(context)).toBe(true);
});
it('should return [false] if url starts with `/favorites`', () => {
const context: any = {
navigation: {
url: '/favorites/path'
}
};
expect(app.isNotFavorites(context)).toBe(false);
});
});
describe('isSharedFiles', () => {
it('should return [true] if path starts with `/shared`', () => {
const context: any = {
@ -103,28 +81,6 @@ describe('navigation.evaluators', () => {
});
});
describe('isNotSharedFiles', () => {
it('should return [true] if path does not contain `/shared`', () => {
const context: any = {
navigation: {
url: '/some/path/'
}
};
expect(app.isNotSharedFiles(context)).toBe(true);
});
it('should return [false] if path contains `/shared`', () => {
const context: any = {
navigation: {
url: '/shared/path/'
}
};
expect(app.isNotSharedFiles(context)).toBe(false);
});
});
describe('isTrashcan', () => {
it('should return [true] if url starts with `/trashcan`', () => {
const context: any = {
@ -147,28 +103,6 @@ describe('navigation.evaluators', () => {
});
});
describe('isNotTrashcan', () => {
it('should return [true] if url does not start with `/trashcan`', () => {
const context: any = {
navigation: {
url: '/path/trashcan'
}
};
expect(app.isNotTrashcan(context)).toBe(true);
});
it('should return [false] if url does start with `/trashcan`', () => {
const context: any = {
navigation: {
url: '/trashcan'
}
};
expect(app.isNotTrashcan(context)).toBe(false);
});
});
describe('isPersonalFiles', () => {
it('should return [true] if url starts with `/personal-files`', () => {
const context: any = {
@ -213,18 +147,6 @@ describe('navigation.evaluators', () => {
});
});
describe('isNotLibraries', () => {
it('should return [true] if url does not end with `/libraries`', () => {
const context: any = {
navigation: {
url: '/libraries/path'
}
};
expect(app.isNotLibraries(context)).toBe(true);
});
});
describe('isDetails', () => {
it('should return true if url includes with `/details`', () => {
const context: any = {
@ -247,28 +169,6 @@ describe('navigation.evaluators', () => {
});
});
describe('isNotDetails', () => {
it('should return true if url does not include `/details`', () => {
const context: any = {
navigation: {
url: '/path'
}
};
expect(app.isNotDetails(context)).toBe(true);
});
it('should return false if url includes `/details`', () => {
const context: any = {
navigation: {
url: 'personal-files/details/path'
}
};
expect(app.isNotDetails(context)).toBe(false);
});
});
describe('isRecentFiles', () => {
it('should return [true] if url starts with `/recent-files`', () => {
const context: any = {
@ -312,90 +212,4 @@ describe('navigation.evaluators', () => {
expect(app.isSearchResults(context)).toBe(false);
});
});
describe('isSharedPreview', () => {
it('should return [true] if url starts with `/shared` and contains `viewer:view', () => {
const context: any = {
navigation: {
url: '/shared/(viewer:view)'
}
};
expect(app.isSharedPreview(context)).toBe(true);
});
it('should return [false] if url does not start with `/shared`', () => {
const context: any = {
navigation: {
url: '/path/shared/(viewer:view)'
}
};
expect(app.isSharedPreview(context)).toBe(false);
});
it('should return [false] if url starts with `/shared` and does not includes `viewer:view`', () => {
const context: any = {
navigation: {
url: '/shared/something'
}
};
expect(app.isSharedPreview(context)).toBe(false);
});
});
describe('isFavoritesPreview', () => {
it('should return [true] if url starts with `/favorites` and includes `viewer:view`', () => {
const context: any = {
navigation: {
url: '/favorites/(viewer:view)'
}
};
expect(app.isFavoritesPreview(context)).toBe(true);
});
it('should return [false] if url does not start with `/favorites`', () => {
const context: any = {
navigation: {
url: '/path/favorites/(viewer:view)'
}
};
expect(app.isFavoritesPreview(context)).toBe(false);
});
it('should return [false] if url starts with `/favorites` and does not include `viewer:view`', () => {
const context: any = {
navigation: {
url: '/favorites/other'
}
};
expect(app.isFavoritesPreview(context)).toBe(false);
});
});
describe('isSharedFileViewer', () => {
it('should return [true] if url starts with `/preview/s/`', () => {
const context: any = {
navigation: {
url: '/preview/s/path'
}
};
expect(app.isSharedFileViewer(context)).toBe(true);
});
it('should return [false] if url does not start with `/preview/s/`', () => {
const context: any = {
navigation: {
url: '/path/preview/s/'
}
};
expect(app.isSharedFileViewer(context)).toBe(false);
});
});
});

View File

@ -42,12 +42,6 @@ export function isFavorites(context: RuleContext): boolean {
return url?.startsWith('/favorites') && !isPreview(context);
}
/**
* Checks if the activated route is not **Favorites**.
* JSON ref: `app.navigation.isNotFavorites`
*/
export const isNotFavorites = (context: RuleContext): boolean => !isFavorites(context);
/**
* Checks if a **Shared Files** route is activated.
* JSON ref: `app.navigation.isSharedFiles`
@ -57,12 +51,6 @@ export function isSharedFiles(context: RuleContext): boolean {
return url?.startsWith('/shared') && !isPreview(context);
}
/**
* Checks if the activated route is not **Shared Files**.
* JSON ref: `app.navigation.isNotSharedFiles`
*/
export const isNotSharedFiles = (context: RuleContext): boolean => !isSharedFiles(context);
/**
* Checks if a **Trashcan** route is activated.
* JSON ref: `app.navigation.isTrashcan`
@ -72,12 +60,6 @@ export function isTrashcan(context: RuleContext): boolean {
return url?.startsWith('/trashcan');
}
/**
* Checks if the activated route is not **Trashcan**.
* JSON ref: `app.navigation.isNotTrashcan`
*/
export const isNotTrashcan = (context: RuleContext): boolean => !isTrashcan(context);
/**
* Checks if a **Personal Files** route is activated.
* JSON ref: `app.navigation.isPersonalFiles`
@ -87,15 +69,6 @@ export function isPersonalFiles(context: RuleContext): boolean {
return url?.startsWith('/personal-files');
}
/**
* Checks if a **Library Files** route is activated.
* JSON ref: `app.navigation.isLibraryFiles`
*/
export function isLibraryFiles(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/libraries');
}
/**
* Checks if a **Library Files** or **Library Search Result** route is activated.
* JSON ref: `app.navigation.isLibraryFiles`
@ -115,18 +88,6 @@ export function isDetails(context: RuleContext): boolean {
return url?.includes('/details');
}
/**
* Checks if the activated route is not **Details**.
* JSON ref: `app.navigation.isNotDetails`
*/
export const isNotDetails = (context: RuleContext): boolean => !isDetails(context);
/**
* Checks if the activated route is neither **Libraries** nor **Library Search Results**.
* JSON ref: `app.navigation.isNotLibraries`
*/
export const isNotLibraries = (context: RuleContext): boolean => !isLibraries(context);
/**
* Checks if a **Recent Files** route is activated.
* JSON ref: `app.navigation.isRecentFiles`
@ -136,12 +97,6 @@ export function isRecentFiles(context: RuleContext): boolean {
return url?.startsWith('/recent-files');
}
/**
* Checks if the activated route is not **Recent Files**.
* JSON ref: `app.navigation.isNotRecentFiles`
*/
export const isNotRecentFiles = (context: RuleContext): boolean => !isRecentFiles(context);
/**
* Checks if a **Search Results** route is activated.
* JSON ref: `app.navigation.isSearchResults`
@ -153,36 +108,3 @@ export function isSearchResults(
const { url } = context.navigation;
return url?.startsWith('/search');
}
/**
* Checks if the activated route is not **Search Results**.
* JSON ref: `app.navigation.isNotSearchResults`
*/
export const isNotSearchResults = (context: RuleContext): boolean => !isSearchResults(context);
/**
* Checks if a **Shared Preview** route is activated.
* JSON ref: `app.navigation.isSharedPreview`
*/
export function isSharedPreview(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/shared/preview/') || (url?.startsWith('/shared') && url?.includes('viewer:view'));
}
/**
* Checks if a **Favorites Preview** route is activated.
* JSON ref: `app.navigation.isFavoritesPreview`
*/
export function isFavoritesPreview(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/favorites/preview/') || (url?.startsWith('/favorites') && url?.includes('viewer:view'));
}
/**
* Checks if a **Shared File Preview** route is activated.
* JSON ref: `app.navigation.isFavoritesPreview`
*/
export function isSharedFileViewer(context: RuleContext): boolean {
const { url } = context.navigation;
return url?.startsWith('/preview/s/');
}