This repository has been archived by the owner on May 7, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(warning-page): Implement new design and action on opt-in button …
…click (#22) * fix(feature-warning): add new design for warning-page * fix(feature-warning): add service for enabling feature * fix(feature-warning): add feature enable action on opt-in button click * fix(feature-toggle): add option to display default/customised warning * fix(feature-toggle): fix tests * fix(feature-toggle): add option to display default/customised warning * fix(feature-warning): add new design for warning page * fix(demo-app): up and running demo app with new changes * fix(demo-app): fix feature toggle mock service
- Loading branch information
1 parent
b94c4a0
commit 8b5f018
Showing
22 changed files
with
819 additions
and
314 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"arrowParens": "always", | ||
"singleQuote": true, | ||
"bracketSpacing": true, | ||
"printWidth": 100 | ||
} |
14 changes: 14 additions & 0 deletions
14
projects/ngx-feature-flag/src/lib/feature-wrapper/feature-toggle.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<ng-container *ngIf="(feature$ | async) as feature"> | ||
<ng-container | ||
[ngTemplateOutlet]=" | ||
isFeatureUserEnabled ? userLevel : showFeatureOptIn ? featureOptInTemplate : defaultLevel | ||
" | ||
></ng-container> | ||
|
||
<ng-template #featureOptInTemplate> | ||
<f8-feature-warning-page | ||
[level]="feature.level" | ||
(onOptInButtonClick)="setUserLevelTemplate()" | ||
></f8-feature-warning-page> | ||
</ng-template> | ||
</ng-container> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 37 additions & 12 deletions
49
projects/ngx-feature-flag/src/lib/feature-wrapper/feature-toggle.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,57 @@ | ||
import { | ||
Component, | ||
Input, | ||
OnInit, | ||
TemplateRef | ||
} from '@angular/core'; | ||
import { Observable } from 'rxjs'; | ||
import { Component, Input, OnInit, TemplateRef } from '@angular/core'; | ||
import { Observable, of } from 'rxjs'; | ||
import { FeatureTogglesService } from '../service/feature-toggles.service'; | ||
import { Feature } from '../models/feature'; | ||
import { map, catchError, tap } from 'rxjs/operators'; | ||
|
||
interface FeatureEnablementData { | ||
enabled: boolean; | ||
level: string; | ||
} | ||
|
||
@Component({ | ||
selector: 'f8-feature-toggle', | ||
template: `<ng-container [ngTemplateOutlet]="(enabled | async) ? userLevel : defaultLevel"></ng-container>` | ||
templateUrl: './feature-toggle.component.html' | ||
}) | ||
export class FeatureToggleComponent implements OnInit { | ||
|
||
@Input() featureName: string; | ||
@Input() userLevel: TemplateRef<any>; | ||
@Input() defaultLevel: TemplateRef<any>; | ||
@Input() showFeatureOptIn: boolean = false; | ||
|
||
enabled: Observable<{} | boolean>; | ||
isFeatureUserEnabled: boolean = false; | ||
feature$: Observable<{} | FeatureEnablementData>; | ||
|
||
constructor(private featureService: FeatureTogglesService) {} | ||
|
||
ngOnInit(): void { | ||
if (!this.featureName) { | ||
throw new Error('Attribute `featureName` should not be null or empty'); | ||
} | ||
|
||
this.enabled = this.featureService.isFeatureUserEnabled(this.featureName); | ||
this.feature$ = this.featureService.getFeature(this.featureName).pipe( | ||
map((feature: Feature) => { | ||
if (feature.attributes) { | ||
let featureEnablementData: FeatureEnablementData = { | ||
enabled: feature.attributes.enabled && feature.attributes['user-enabled'], | ||
level: feature.attributes['enablement-level'] | ||
}; | ||
return featureEnablementData; | ||
} else { | ||
return {}; | ||
} | ||
}), | ||
tap((feature: FeatureEnablementData) => { | ||
if (feature && feature.enabled) { | ||
this.isFeatureUserEnabled = true; | ||
} else { | ||
this.isFeatureUserEnabled = false; | ||
} | ||
}), | ||
catchError(() => of({})) | ||
); | ||
} | ||
|
||
setUserLevelTemplate() { | ||
this.isFeatureUserEnabled = true; | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
projects/ngx-feature-flag/src/lib/service/enable-feature.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { | ||
HttpClientTestingModule, | ||
HttpTestingController, | ||
TestRequest | ||
} from '@angular/common/http/testing'; | ||
import { async, getTestBed, TestBed } from '@angular/core/testing'; | ||
import { AuthenticationService } from 'ngx-login-client'; | ||
import { first } from 'rxjs/operators'; | ||
import { FABRIC8_FEATURE_TOGGLES_API_URL } from './feature-toggles.service'; | ||
import { EnableFeatureService, ExtProfile, ExtUser } from './enable-feature.service'; | ||
import { Logger } from 'ngx-base'; | ||
import { UserService } from 'ngx-login-client'; | ||
|
||
describe('EnableFeature service:', () => { | ||
let mockAuthService: jasmine.SpyObj<AuthenticationService>; | ||
let mockUserService: jasmine.SpyObj<UserService>; | ||
let enableFeatureService: EnableFeatureService; | ||
let httpTestingController: HttpTestingController; | ||
|
||
let url: string; | ||
|
||
beforeEach(() => { | ||
mockAuthService = jasmine.createSpyObj('AuthenticationService', ['getToken']); | ||
mockAuthService.getToken.and.returnValue('mock-auth-token'); | ||
mockUserService = jasmine.createSpyObj('UserService', ['loggedInUser']); | ||
TestBed.configureTestingModule({ | ||
imports: [HttpClientTestingModule], | ||
providers: [ | ||
{ | ||
provide: AuthenticationService, | ||
useValue: mockAuthService | ||
}, | ||
{ | ||
provide: FABRIC8_FEATURE_TOGGLES_API_URL, | ||
useValue: 'http://example.com/api/' | ||
}, | ||
{ | ||
provide: UserService, | ||
useValue: mockUserService | ||
}, | ||
EnableFeatureService, | ||
Logger | ||
] | ||
}); | ||
enableFeatureService = getTestBed().get(EnableFeatureService); | ||
httpTestingController = getTestBed().get(HttpTestingController); | ||
|
||
url = getTestBed().get(FABRIC8_FEATURE_TOGGLES_API_URL); | ||
}); | ||
|
||
afterEach(() => { | ||
httpTestingController.verify(); | ||
}); | ||
|
||
it('should be instantiable', async((): void => { | ||
expect(enableFeatureService).toBeDefined(); | ||
})); | ||
|
||
describe('#getUpdate', () => { | ||
it('should send a PATCH', (done: DoneFn): void => { | ||
enableFeatureService | ||
.update({} as ExtProfile) | ||
.pipe(first()) | ||
.subscribe( | ||
(): void => { | ||
done(); | ||
} | ||
); | ||
const req: TestRequest = httpTestingController.expectOne('http://example.com/api/users'); | ||
expect(req.request.method).toEqual('PATCH'); | ||
req.flush({}); | ||
}); | ||
|
||
it('should send correct headers', (done: DoneFn): void => { | ||
enableFeatureService | ||
.update({} as ExtProfile) | ||
.pipe(first()) | ||
.subscribe( | ||
(): void => { | ||
done(); | ||
} | ||
); | ||
const req: TestRequest = httpTestingController.expectOne('http://example.com/api/users'); | ||
expect(req.request.headers.get('Authorization')).toEqual('Bearer mock-auth-token'); | ||
req.flush({}); | ||
}); | ||
|
||
it('should send correct payload', (done: DoneFn): void => { | ||
const attributes: ExtProfile = { featureLevel: 'beta' } as ExtProfile; | ||
enableFeatureService | ||
.update(attributes) | ||
.pipe(first()) | ||
.subscribe( | ||
(): void => { | ||
done(); | ||
} | ||
); | ||
const req: TestRequest = httpTestingController.expectOne('http://example.com/api/users'); | ||
expect(req.request.body).toEqual( | ||
JSON.stringify({ | ||
data: { | ||
attributes, | ||
type: 'identities' | ||
} | ||
}) | ||
); | ||
req.flush({}); | ||
}); | ||
|
||
it('should return expected data', (done: DoneFn): void => { | ||
const data: ExtUser = { | ||
attributes: { | ||
featureLevel: 'beta' | ||
} | ||
} as ExtUser; | ||
enableFeatureService | ||
.update(data.attributes) | ||
.pipe(first()) | ||
.subscribe( | ||
(user: ExtUser): void => { | ||
expect(user).toEqual(data); | ||
done(); | ||
} | ||
); | ||
httpTestingController.expectOne('http://example.com/api/users').flush({ data }); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.