[ACA-543] Disable Share feature based on repository property (#658)

* add visibility type to navigation schema

* repository data model

* add repository state

* add repository state

* add repository evaluator

* allot navigation elements to called again

* AppRuleContext extends

* repository status resolver

* map repository evaluator

* reevaluate navigation elements

* generic route evaluator

* add route guard on Share

* evaluate guard based on data property

* changed old imports

* add Action suffix

* set state false by default

* tree shakable services to simplify rebase

* fix rebase

* isQuickShareEnabled initial null state

* repository store effects

* repository store actions

* refactored

* refactored
This commit is contained in:
Cilibiu Bogdan 2018-09-28 12:32:36 +03:00 committed by Denys Vuika
parent 30863e7bdd
commit 8d9d3dbc45
21 changed files with 517 additions and 39 deletions

View File

@ -199,9 +199,15 @@
"description": "Element order",
"type": "number"
},
"disabled": {
"description": "Toggles the disabled state",
"type": "boolean"
"rules": {
"description": "Element rules",
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
}
}
}
}
},
@ -223,9 +229,15 @@
"description": "Group order",
"type": "number"
},
"disabled": {
"description": "Toggles the disabled state",
"type": "boolean"
"rules": {
"description": "Element rules",
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
}
}
}
}
},

View File

@ -30,8 +30,10 @@ import { LibrariesComponent } from './components/libraries/libraries.component';
import { GenericErrorComponent } from './components/common/generic-error/generic-error.component';
import { SearchResultsComponent } from './components/search/search-results/search-results.component';
import { ProfileResolver } from './services/profile.resolver';
import { RepositoryStatusResolver } from './services/repository-status.resolver';
import { LoginComponent } from './components/login/login.component';
import { AppAuthGuard } from './guards/auth.guard';
import { AppSharedRuleGuard } from './guards/shared.guard';
export const APP_ROUTES: Routes = [
{
@ -54,7 +56,10 @@ export const APP_ROUTES: Routes = [
{
path: '',
component: LayoutComponent,
resolve: { profile: ProfileResolver },
resolve: {
profile: ProfileResolver,
repository: RepositoryStatusResolver
},
children: [
{
path: '',
@ -196,7 +201,9 @@ export const APP_ROUTES: Routes = [
navigateSource: 'shared'
}
}
]
],
canActivateChild: [AppSharedRuleGuard],
canActivate: [AppSharedRuleGuard]
},
{
path: 'trashcan',

View File

@ -23,9 +23,20 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import {
Component,
Input,
OnInit,
ViewEncapsulation,
OnDestroy
} from '@angular/core';
import { AppExtensionService } from '../../extensions/extension.service';
import { NavBarGroupRef } from '@alfresco/adf-extensions';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states';
import { ruleContext } from '../../store/selectors/app.selectors';
import { Subject } from 'rxjs';
import { takeUntil, distinctUntilChanged, map } from 'rxjs/operators';
@Component({
selector: 'app-sidenav',
@ -34,19 +45,40 @@ import { NavBarGroupRef } from '@alfresco/adf-extensions';
encapsulation: ViewEncapsulation.None,
host: { class: 'app-sidenav' }
})
export class SidenavComponent implements OnInit {
export class SidenavComponent implements OnInit, OnDestroy {
private onDestroy$: Subject<boolean> = new Subject<boolean>();
@Input()
showLabel: boolean;
groups: Array<NavBarGroupRef> = [];
constructor(private extensions: AppExtensionService) {}
constructor(
private store: Store<AppStore>,
private extensions: AppExtensionService
) {}
ngOnInit() {
this.groups = this.extensions.getNavigationGroups();
this.store
.select(ruleContext)
.pipe(
takeUntil(this.onDestroy$),
map(rules => rules.repository),
distinctUntilChanged()
)
.subscribe(() => {
this.groups = this.extensions.getApplicationNavigation(
this.extensions.navbar
);
});
}
trackById(index: number, obj: { id: string }) {
return obj.id;
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
}

View File

@ -0,0 +1,31 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleContext } from '@alfresco/adf-extensions';
import { RepositoryState } from '../store/states';
export interface AppRuleContext extends RuleContext {
repository: RepositoryState;
}

View File

@ -27,6 +27,7 @@ import { CoreModule } from '@alfresco/adf-core';
import { CommonModule } from '@angular/common';
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { LayoutComponent } from '../components/layout/layout.component';
import * as repository from './evaluators/repository.evaluators';
import * as app from './evaluators/app.evaluators';
import * as nav from './evaluators/navigation.evaluators';
import { AppExtensionService } from './extension.service';
@ -109,7 +110,9 @@ export class CoreExtensionsModule {
'app.navigation.isNotRecentFiles': nav.isNotRecentFiles,
'app.navigation.isSearchResults': nav.isSearchResults,
'app.navigation.isNotSearchResults': nav.isNotSearchResults,
'app.navigation.isPreview': nav.isPreview
'app.navigation.isPreview': nav.isPreview,
'repository.isQuickShareEnabled': repository.hasQuickShareEnabled
});
}
}

View File

@ -0,0 +1,34 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleParameter } from '@alfresco/adf-extensions';
import { AppRuleContext } from '../app.interface';
export function hasQuickShareEnabled(
context: AppRuleContext,
...args: RuleParameter[]
): boolean {
return context.repository.isQuickShareEnabled;
}

View File

@ -26,7 +26,7 @@
import { Injectable, Type } from '@angular/core';
import { Store } from '@ngrx/store';
import { Route } from '@angular/router';
import { AppStore } from '../store/states';
import { AppStore, RepositoryState } from '../store/states';
import { ruleContext } from '../store/selectors/app.selectors';
import { NodePermissionService } from '../services/node-permission.service';
import { ProfileResolver } from '../services/profile.resolver';
@ -75,6 +75,7 @@ export class AppExtensionService implements RuleContext {
selection: SelectionState;
navigation: NavigationState;
profile: ProfileState;
repository: RepositoryState;
constructor(
private store: Store<AppStore>,
@ -87,6 +88,7 @@ export class AppExtensionService implements RuleContext {
this.selection = result.selection;
this.navigation = result.navigation;
this.profile = result.profile;
this.repository = result.repository;
});
}
@ -141,16 +143,17 @@ export class AppExtensionService implements RuleContext {
}
protected loadNavBar(config: ExtensionConfig): Array<NavBarGroupRef> {
const elements = this.loader.getElements<NavBarGroupRef>(
config,
'features.navbar'
);
return this.loader.getElements<NavBarGroupRef>(config, 'features.navbar');
}
getApplicationNavigation(elements) {
return elements.map(group => {
return {
...group,
items: (group.items || [])
.filter(item => !item.disabled)
.filter(item => {
return this.filterByRules(item);
})
.sort(sortByOrder)
.map(item => {
const routeRef = this.extensions.getRouteById(item.route);

View File

@ -0,0 +1,79 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { ActivatedRouteSnapshot } from '@angular/router';
import { tap, map, filter, take } from 'rxjs/operators';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { AppStore } from '../store/states/app.state';
import { GetRepositoryStatusAction } from '../store/actions';
import { repositoryStatus } from '../store/selectors/app.selectors';
@Injectable({
providedIn: 'root'
})
export class AppSharedRuleGuard implements CanActivate {
constructor(private store: Store<AppStore>, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
this.init();
return this.wait();
}
canActivateChild(
route: ActivatedRouteSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.canActivate(route);
}
wait(): Observable<boolean> {
return this.store.select(repositoryStatus).pipe(
map(state => state.isQuickShareEnabled),
filter(value => value !== null),
tap(value => {
if (value === false) {
this.router.navigate(['']);
}
}),
take(1)
);
}
init() {
this.store
.select(repositoryStatus)
.pipe(take(1))
.subscribe(state => {
if (state.isQuickShareEnabled === null) {
this.store.dispatch(new GetRepositoryStatusAction());
}
});
}
}

View File

@ -0,0 +1,70 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Store } from '@ngrx/store';
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable } from 'rxjs';
import { AppStore } from '../store/states/app.state';
import { RepositoryState } from '../store/states';
import { repositoryStatus } from '../store/selectors/app.selectors';
import { filter, take } from 'rxjs/operators';
import { GetRepositoryStatusAction } from '../store/actions';
@Injectable({
providedIn: 'root'
})
export class RepositoryStatusResolver implements Resolve<RepositoryState> {
constructor(private store: Store<AppStore>) {}
resolve(): Observable<RepositoryState> {
this.init();
return this.wait();
}
wait(): Observable<any> {
return this.store.select(repositoryStatus).pipe(
filter(state => this.hasValuesSet(state)),
take(1)
);
}
init() {
this.store
.select(repositoryStatus)
.pipe(take(1))
.subscribe(state => {
if (!this.hasValuesSet(state)) {
this.store.dispatch(new GetRepositoryStatusAction());
}
});
}
private hasValuesSet(object): boolean {
return Object.keys(object)
.map(key => object[key])
.every(value => value !== null);
}
}

View File

@ -33,3 +33,4 @@ export * from './actions/search.actions';
export * from './actions/library.actions';
export * from './actions/upload.actions';
export * from './actions/modals.actions';
export * from './actions/repository.actions';

View File

@ -0,0 +1,38 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Action } from '@ngrx/store';
export const SET_REPOSITORY_STATUS = 'SET_REPOSITORY_STATUS';
export const GET_REPOSITORY_STATUS = 'GET_REPOSITORY_STATUS';
export class SetRepositoryStatusAction implements Action {
readonly type = SET_REPOSITORY_STATUS;
constructor(public payload: any) {}
}
export class GetRepositoryStatusAction implements Action {
readonly type = GET_REPOSITORY_STATUS;
}

View File

@ -42,7 +42,8 @@ import {
LibraryEffects,
UploadEffects,
FavoriteEffects,
ModalsEffects
ModalsEffects,
RepositoryEffects
} from './effects';
@NgModule({
@ -60,7 +61,8 @@ import {
LibraryEffects,
UploadEffects,
FavoriteEffects,
ModalsEffects
ModalsEffects,
RepositoryEffects
]),
!environment.production
? StoreDevtoolsModule.instrument({ maxAge: 25 })

View File

@ -34,3 +34,4 @@ export * from './effects/search.effects';
export * from './effects/library.effects';
export * from './effects/upload.effects';
export * from './effects/modals.effects';
export * from './effects/repository.effects';

View File

@ -0,0 +1,67 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Injectable } from '@angular/core';
import { DiscoveryEntry } from 'alfresco-js-api';
import { map, switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import {
GET_REPOSITORY_STATUS,
GetRepositoryStatusAction
} from '../actions/repository.actions';
import { Router } from '@angular/router';
import { ContentApiService } from '../../services/content-api.service';
import { SetRepositoryStatusAction } from '../actions';
@Injectable()
export class RepositoryEffects {
constructor(
private actions$: Actions,
private router: Router,
private contentApi: ContentApiService
) {}
@Effect()
getRepositoryStatus$ = this.actions$.pipe(
ofType<GetRepositoryStatusAction>(GET_REPOSITORY_STATUS),
switchMap(() => {
return this.contentApi.getRepositoryInformation().pipe(
map(
(response: DiscoveryEntry) =>
new SetRepositoryStatusAction(response.entry.repository.status)
),
catchError(error => {
this.redirectToLogin();
return of(error);
})
);
})
);
private redirectToLogin() {
this.router.navigate(['login']);
}
}

View File

@ -30,6 +30,8 @@ import {
SetSelectedNodesAction,
SET_USER_PROFILE,
SetUserProfileAction,
SET_REPOSITORY_STATUS,
SetRepositoryStatusAction,
SET_LANGUAGE_PICKER,
SetLanguagePickerAction,
SET_CURRENT_FOLDER,
@ -79,6 +81,11 @@ export function appReducer(
action
));
break;
case SET_REPOSITORY_STATUS:
newState = updateRepositoryStatus(state, <SetRepositoryStatusAction>(
action
));
break;
default:
newState = Object.assign({}, state);
}
@ -207,3 +214,12 @@ function updateSelectedNodes(
};
return newState;
}
function updateRepositoryStatus(
state: AppState,
action: SetRepositoryStatusAction
) {
const newState = Object.assign({}, state);
newState.repository = action.payload;
return newState;
}

View File

@ -59,16 +59,22 @@ export const documentDisplayMode = createSelector(
selectApp,
state => state.documentDisplayMode
);
export const repositoryStatus = createSelector(
selectApp,
state => state.repository
);
export const ruleContext = createSelector(
appSelection,
appNavigation,
selectUser,
(selection, navigation, profile) => {
repositoryStatus,
(selection, navigation, profile, repository) => {
return {
selection,
navigation,
profile
profile,
repository
};
}
);

View File

@ -24,3 +24,4 @@
*/
export * from './states/app.state';
export * from './states/repository.state';

View File

@ -28,6 +28,7 @@ import {
ProfileState,
NavigationState
} from '@alfresco/adf-extensions';
import { RepositoryState } from '../states';
export interface AppState {
appName: string;
@ -40,6 +41,7 @@ export interface AppState {
navigation: NavigationState;
infoDrawerOpened: boolean;
documentDisplayMode: string;
repository: RepositoryState;
}
export const INITIAL_APP_STATE: AppState = {
@ -64,7 +66,10 @@ export const INITIAL_APP_STATE: AppState = {
currentFolder: null
},
infoDrawerOpened: false,
documentDisplayMode: 'list'
documentDisplayMode: 'list',
repository: {
isQuickShareEnabled: null
}
};
export interface AppStore {

View File

@ -0,0 +1,31 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
export interface RepositoryState {
isAuditEnabled?: boolean;
isQuickShareEnabled?: boolean;
isReadOnly?: boolean;
isThumbnailGenerationEnabled?: boolean;
}

View File

@ -20,18 +20,36 @@
"type": "rule",
"value": "core.some",
"parameters": [
{ "type": "rule", "value": "app.selection.canAddFavorite" },
{ "type": "rule", "value": "app.selection.canRemoveFavorite" }
{
"type": "rule",
"value": "app.selection.canAddFavorite"
},
{
"type": "rule",
"value": "app.selection.canRemoveFavorite"
}
]
},
{
"type": "rule",
"value": "core.some",
"parameters": [
{ "type": "rule", "value": "app.navigation.isRecentFiles" },
{ "type": "rule", "value": "app.navigation.isSharedFiles" },
{ "type": "rule", "value": "app.navigation.isSearchResults" },
{ "type": "rule", "value": "app.navigation.isFavorites" }
{
"type": "rule",
"value": "app.navigation.isRecentFiles"
},
{
"type": "rule",
"value": "app.navigation.isSharedFiles"
},
{
"type": "rule",
"value": "app.navigation.isSearchResults"
},
{
"type": "rule",
"value": "app.navigation.isFavorites"
}
]
}
]
@ -43,7 +61,10 @@
{ "type": "rule", "value": "app.selection.canAddFavorite" },
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
{ "type": "rule", "value": "app.navigation.isNotSearchResults" },
{
"type": "rule",
"value": "app.navigation.isNotSearchResults"
},
{ "type": "rule", "value": "app.navigation.isNotFavorites" }
]
},
@ -54,7 +75,10 @@
{ "type": "rule", "value": "app.selection.canRemoveFavorite" },
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
{ "type": "rule", "value": "app.navigation.isNotSearchResults" },
{
"type": "rule",
"value": "app.navigation.isNotSearchResults"
},
{ "type": "rule", "value": "app.navigation.isNotFavorites" }
]
},
@ -227,7 +251,10 @@
"icon": "people",
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
"description": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
"route": "shared"
"route": "shared",
"rules": {
"visible": "repository.isQuickShareEnabled"
}
},
{
"id": "app.navbar.recentFiles",

View File

@ -199,9 +199,15 @@
"description": "Element order",
"type": "number"
},
"disabled": {
"description": "Toggles the disabled state",
"type": "boolean"
"rules": {
"description": "Element rules",
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
}
}
}
}
},
@ -223,9 +229,15 @@
"description": "Group order",
"type": "number"
},
"disabled": {
"description": "Toggles the disabled state",
"type": "boolean"
"rules": {
"description": "Element rules",
"type": "object",
"properties": {
"visible": {
"description": "Rule to evaluate the visibility state",
"type": "string"
}
}
}
}
},