Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General refactor for Angular #2261

Merged
merged 10 commits into from
Nov 27, 2019
1 change: 1 addition & 0 deletions npm/ng-packs/packages/core/src/lib/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { ConfigState } from './states/config.state';
import { ProfileState } from './states/profile.state';
import { SessionState } from './states/session.state';
import { getInitialData, localeInitializer } from './utils/initial-utils';
import './utils/date-extensions';

@NgModule({
imports: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,68 @@
import { Directive, ElementRef, Input, OnDestroy, OnInit, Optional, Renderer2 } from '@angular/core';
import {
Directive,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
ViewContainerRef,
TemplateRef,
Optional,
SimpleChanges,
OnChanges,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { ConfigState } from '../states';
import { takeUntilDestroy } from '../utils';
import { Subscription } from 'rxjs';

@Directive({
selector: '[abpPermission]',
})
export class PermissionDirective implements OnInit, OnDestroy {
export class PermissionDirective implements OnInit, OnDestroy, OnChanges {
@Input('abpPermission') condition: string;

constructor(@Optional() private elRef: ElementRef, private renderer: Renderer2, private store: Store) {}
subscription: Subscription;

constructor(
private elRef: ElementRef,
private renderer: Renderer2,
private store: Store,
@Optional() private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
) {}

private check() {
if (this.subscription) {
this.subscription.unsubscribe();
}

this.subscription = this.store
.select(ConfigState.getGrantedPolicy(this.condition))
.pipe(takeUntilDestroy(this))
.subscribe(isGranted => {
if (this.templateRef && isGranted) {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.templateRef);
} else if (this.templateRef && !isGranted) {
this.vcRef.clear();
} else if (!isGranted && !this.templateRef) {
this.renderer.removeChild((this.elRef.nativeElement as HTMLElement).parentElement, this.elRef.nativeElement);
}
});
}

ngOnInit() {
if (this.condition) {
this.store
.select(ConfigState.getGrantedPolicy(this.condition))
.pipe(takeUntilDestroy(this))
.subscribe(isGranted => {
if (!isGranted) {
this.renderer.removeChild(
(this.elRef.nativeElement as HTMLElement).parentElement,
this.elRef.nativeElement,
);
}
});
if (this.templateRef && !this.condition) {
this.vcRef.createEmbeddedView(this.templateRef);
}
}

ngOnDestroy(): void {}

ngOnChanges({ condition }: SimpleChanges) {
if ((condition || { currentValue: null }).currentValue) {
this.check();
}
}
}
12 changes: 9 additions & 3 deletions npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
Expand All @@ -13,8 +13,14 @@ import { ConfigState } from '../states';
export class PermissionGuard implements CanActivate {
constructor(private store: Store) {}

canActivate({ data }: ActivatedRouteSnapshot): Observable<boolean> {
const resource = snq(() => data.routes.requiredPolicy) || (data.requiredPolicy as string);
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
let resource = snq(() => route.data.routes.requiredPolicy) || snq(() => route.data.requiredPolicy as string);
if (!resource) {
resource = snq(
() => route.routeConfig.children.find(child => state.url.indexOf(child.path) > -1).data.requiredPolicy,
);
}

return this.store.select(ConfigState.getGrantedPolicy(resource)).pipe(
tap(access => {
if (!access) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,43 @@ describe('PermissionDirective', () => {
expect(spy.mock.calls).toHaveLength(0);
});
});

describe('structural', () => {
beforeEach(() => {
spectator = createDirective(
'<div id="test-element" *abpPermission="condition">Testing Permission Directive</div>',
{ hostProps: { condition: '' } },
);
directive = spectator.directive;
});

it('should be created', () => {
expect(directive).toBeTruthy();
});

it('should remove the element from DOM', () => {
expect(spectator.query('#test-element')).toBeTruthy();
expect(spectator.directive.subscription).toBeUndefined();
spectator.setHostInput({ condition: 'test' });
expect(spectator.directive.subscription).toBeTruthy();
grantedPolicy$.next(true);
expect(spectator.query('#test-element')).toBeTruthy();
grantedPolicy$.next(false);
expect(spectator.query('#test-element')).toBeFalsy();
grantedPolicy$.next(true);
grantedPolicy$.next(true);
expect(spectator.queryAll('#test-element')).toHaveLength(1);
});

describe('#subscription', () => {
it('should call the unsubscribe', () => {
const spy = jest.fn(() => {});
spectator.setHostInput({ condition: 'test' });
spectator.directive.subscription.unsubscribe = spy;
spectator.setHostInput({ condition: 'test2' });

expect(spy).toHaveBeenCalled();
});
});
});
});
18 changes: 16 additions & 2 deletions npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('PermissionGuard', () => {
it('should return true when the grantedPolicy is true', done => {
store.select.andReturn(of(true));
const spy = jest.spyOn(store, 'dispatch');
guard.canActivate({ data: { requiredPolicy: '' } } as any).subscribe(res => {
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(true);
expect(spy.mock.calls).toHaveLength(0);
done();
Expand All @@ -33,11 +33,25 @@ describe('PermissionGuard', () => {
it('should return false and dispatch RestOccurError when the grantedPolicy is false', done => {
store.select.andReturn(of(false));
const spy = jest.spyOn(store, 'dispatch');
guard.canActivate({ data: { requiredPolicy: '' } } as any).subscribe(res => {
guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => {
expect(res).toBe(false);
expect(spy.mock.calls[0][0] instanceof RestOccurError).toBeTruthy();
expect((spy.mock.calls[0][0] as RestOccurError).payload).toEqual({ status: 403 });
done();
});
});

it('should find the requiredPolicy from child route', done => {
store.select.andReturn(of(false));
const spy = jest.spyOn(store, 'select');
guard
.canActivate(
{ data: {}, routeConfig: { children: [{ path: 'test', data: { requiredPolicy: 'TestPolicy' } }] } } as any,
{ url: 'test' } as any,
)
.subscribe(() => {
expect(spy.mock.calls[0][0]({ auth: { grantedPolicies: { TestPolicy: true } } })).toBe(true);
done();
});
});
});
13 changes: 13 additions & 0 deletions npm/ng-packs/packages/core/src/lib/utils/date-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export {};

declare global {
interface Date {
toLocalISOString(): string;
}
}

Date.prototype.toLocalISOString = function(this: Date): string {
const timezoneOffset = this.getTimezoneOffset();

return new Date(this.getTime() - timezoneOffset * 60000).toISOString();
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h5 class="card-title">{{ 'AbpIdentity::Roles' | abpLocalization }}</h5>
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpIdentity.Roles.Create'"
*abpPermission="'AbpIdentity.Roles.Create'"
id="create-role"
class="btn btn-primary"
type="button"
Expand Down Expand Up @@ -73,18 +73,18 @@ <h5 class="card-title">{{ 'AbpIdentity::Roles' | abpLocalization }}</h5>
<i class="fa fa-cog mr-1"></i>{{ 'AbpIdentity::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button [abpPermission]="'AbpIdentity.Roles.Update'" ngbDropdownItem (click)="edit(data.id)">
<button *abpPermission="'AbpIdentity.Roles.Update'" ngbDropdownItem (click)="edit(data.id)">
{{ 'AbpIdentity::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Roles.ManagePermissions'"
*abpPermission="'AbpIdentity.Roles.ManagePermissions'"
ngbDropdownItem
(click)="providerKey = data.name; visiblePermissions = true"
>
{{ 'AbpIdentity::Permissions' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Roles.Delete'"
*abpPermission="'AbpIdentity.Roles.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.name)"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h5 class="card-title">{{ 'AbpIdentity::Users' | abpLocalization }}</h5>
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpIdentity.Users.Create'"
*abpPermission="'AbpIdentity.Users.Create'"
id="create-role"
class="btn btn-primary"
type="button"
Expand Down Expand Up @@ -86,18 +86,18 @@ <h5 class="card-title">{{ 'AbpIdentity::Users' | abpLocalization }}</h5>
<i class="fa fa-cog mr-1"></i>{{ 'AbpIdentity::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button [abpPermission]="'AbpIdentity.Users.Update'" ngbDropdownItem (click)="edit(data.id)">
<button *abpPermission="'AbpIdentity.Users.Update'" ngbDropdownItem (click)="edit(data.id)">
{{ 'AbpIdentity::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Users.ManagePermissions'"
*abpPermission="'AbpIdentity.Users.ManagePermissions'"
ngbDropdownItem
(click)="providerKey = data.id; visiblePermissions = true"
>
{{ 'AbpIdentity::Permissions' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpIdentity.Users.Delete'"
*abpPermission="'AbpIdentity.Users.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.userName)"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h5 class="card-title">{{ 'AbpTenantManagement::Tenants' | abpLocalization }}</h
</div>
<div class="text-right col col-md-6">
<button
[abpPermission]="'AbpTenantManagement.Tenants.Create'"
*abpPermission="'AbpTenantManagement.Tenants.Create'"
id="create-tenants"
class="btn btn-primary"
type="button"
Expand Down Expand Up @@ -79,28 +79,28 @@ <h5 class="card-title">{{ 'AbpTenantManagement::Tenants' | abpLocalization }}</h
</button>
<div ngbDropdownMenu>
<button
[abpPermission]="'AbpTenantManagement.Tenants.Update'"
*abpPermission="'AbpTenantManagement.Tenants.Update'"
ngbDropdownItem
(click)="editTenant(data.id)"
>
{{ 'AbpTenantManagement::Edit' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.ManageConnectionStrings'"
*abpPermission="'AbpTenantManagement.Tenants.ManageConnectionStrings'"
ngbDropdownItem
(click)="onEditConnectionString(data.id)"
>
{{ 'AbpTenantManagement::Permission:ManageConnectionStrings' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.ManageFeatures'"
*abpPermission="'AbpTenantManagement.Tenants.ManageFeatures'"
ngbDropdownItem
(click)="providerKey = data.id; visibleFeatures = true"
>
{{ 'AbpTenantManagement::Permission:ManageFeatures' | abpLocalization }}
</button>
<button
[abpPermission]="'AbpTenantManagement.Tenants.Delete'"
*abpPermission="'AbpTenantManagement.Tenants.Delete'"
ngbDropdownItem
(click)="delete(data.id, data.name)"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,13 @@ export class TenantsComponent implements OnInit {
get isDisabledSaveButton(): boolean {
if (!this.selectedModalContent) return false;

if (this.selectedModalContent.type === 'saveConnStr' && this.defaultConnectionStringForm.invalid) {
if (
this.selectedModalContent.type === 'saveConnStr' &&
this.defaultConnectionStringForm &&
this.defaultConnectionStringForm.invalid
) {
return true;
} else if (this.selectedModalContent.type === 'saveTenant' && this.tenantForm.invalid) {
} else if (this.selectedModalContent.type === 'saveTenant' && this.tenantForm && this.tenantForm.invalid) {
return true;
} else {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</ng-container>

<ng-template #defaultLink let-route>
<li class="nav-item" [abpPermission]="route.requiredPolicy">
<li class="nav-item" *abpPermission="route.requiredPolicy">
<a class="nav-link" [routerLink]="[route.url]"
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i> {{ route.name | abpLocalization }}</a
>
Expand All @@ -42,7 +42,7 @@
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
[abpPermission]="route.requiredPolicy"
*abpPermission="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
Expand Down Expand Up @@ -86,7 +86,7 @@
</ng-template>

<ng-template #defaultChild let-child>
<div class="dropdown-submenu" [abpPermission]="child.requiredPolicy">
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy">
<a class="dropdown-item" [routerLink]="[child.url]">
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}</a
Expand All @@ -103,7 +103,7 @@
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[autoClose]="true"
[abpPermission]="child.requiredPolicy"
*abpPermission="child.requiredPolicy"
(openChange)="openChange($event, childrenContainer)"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div #container id="abp-error" class="error">
<div #container id="abp-http-error-container" class="error" [style.backgroundColor]="backgroundColor">
<button *ngIf="!hideCloseIcon" id="abp-close-button" type="button" class="close mr-2" (click)="destroy()">
<span aria-hidden="true">&times;</span>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.error {
position: fixed;
top: 0;
background-color: #fff;
width: 100vw;
height: 100vh;
z-index: 999999;
Expand Down
Loading