Skip to content

Commit

Permalink
Merged in feature/SHIBUI-331 (pull request #155)
Browse files Browse the repository at this point in the history
SHIBUI-331 Implemented unsaved changes modal in provider editor

* SHIBUI-331 Implemented unsaved changes modal in provider editor

Approved-by: Shibui Jenkins <shibui.jenkins@gmail.com>
Approved-by: Ryan Mathis <rmathis@unicon.net>
  • Loading branch information
rmathis committed Aug 14, 2018
1 parent 4b0f3f6 commit 10c1241
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ describe('EntityAttributesFilter Entity', () => {
expect(entity).toBeDefined();
expect(entity.resourceId).toBe('foo');
expect(entity.enabled).toBe(entity.filterEnabled);
expect(entity.id).toBe(entity.resourceId);
expect(entity.getId()).toBe(entity.entityId);
expect(entity.getDisplayId()).toBe(entity.entityId);
expect(entity.isDraft()).toBe(false);
});
});
6 changes: 5 additions & 1 deletion ui/src/app/metadata/domain/service/provider.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { MetadataProvider } from '../../domain/model';
import { FileBackedHttpMetadataProvider } from '../model/providers';
import { ProviderOrder } from '../model/metadata-order';


@Injectable()
export class MetadataProviderService {

Expand All @@ -17,7 +19,9 @@ export class MetadataProviderService {
private http: HttpClient
) {}
query(): Observable<MetadataProvider[]> {
return this.http.get<MetadataProvider[]>(`${this.base}${this.endpoint}`, {});
return this.http.get<MetadataProvider[]>(`${this.base}${this.endpoint}`).pipe(
map(providers => providers.filter(p => p['@type'] !== 'BaseMetadataResolver'))
);
}

find(id: string): Observable<MetadataProvider> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class DashboardProvidersListComponent implements OnInit {
ngOnInit(): void {
this.providers$ = this.store.select(getOrderedProviders);
this.providersOpen$ = this.store.select(getOpenProviders);
this.providers$.subscribe(p => console.log(p));
}

view(id: string, page: string): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<ng-container>
<div aria-label="Save your information? modal" role="region" tabindex="0">
<div class="modal-header">
<h4 class="modal-title" i18n="@@message--unsaved-dialog-title">Save your information?</h4>
</div>
<div class="modal-body">
<p i18n="@@message--unsaved-editor">You have not saved your changes. If you exit this screen, your changes will be lost.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="this.close()">
<span i18n="@@action--discard-changes" aria-label="Discard changes, go back to dashboard">Discard Changes</span>
</button>
<button type="button" class="btn btn-secondary" (click)="this.dismiss()" i18n="@@action--cancel" aria-label="Close modal and go back to editing">Cancel</button>
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, ViewChild } from '@angular/core';
import { TestBed, async, ComponentFixture} from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { SharedModule } from '../../../shared/shared.module';
import { UnsavedProviderComponent } from './unsaved-provider.dialog';
import { NgbActiveModalStub } from '../../../../testing/modal.stub';

@Component({
template: `
<unsaved-provider></unsaved-provider>
`
})
class TestHostComponent {
@ViewChild(UnsavedProviderComponent)
public componentUnderTest: UnsavedProviderComponent;
}

describe('Unsaved Provider Dialog Component', () => {

let fixture: ComponentFixture<TestHostComponent>;
let instance: TestHostComponent;
let cmp: UnsavedProviderComponent;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [
UnsavedProviderComponent,
TestHostComponent
],
providers: [
{ provide: NgbActiveModal, useClass: NgbActiveModalStub }
]
}).compileComponents();

fixture = TestBed.createComponent(TestHostComponent);
instance = fixture.componentInstance;
cmp = instance.componentUnderTest;
fixture.detectChanges();
}));

it('should instantiate the component', async(() => {
expect(cmp).toBeTruthy();
}));
});
27 changes: 27 additions & 0 deletions ui/src/app/metadata/provider/component/unsaved-provider.dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, Input } from '@angular/core';

import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Store, Action } from '@ngrx/store';
import { Subject } from 'rxjs/Subject';

import * as fromEditor from '../reducer';

@Component({
selector: 'unsaved-provider',
templateUrl: './unsaved-provider.dialog.html'
})
export class UnsavedProviderComponent {
readonly subject: Subject<boolean> = new Subject<boolean>();

constructor(
public activeModal: NgbActiveModal
) { }

close(): void {
this.activeModal.close();
}

dismiss(): void {
this.activeModal.dismiss();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Component, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Router, ActivatedRouteSnapshot } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { TestBed, async, ComponentFixture, fakeAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { StoreModule, Store, combineReducers } from '@ngrx/store';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ProviderEditComponent } from './provider-edit.component';
import * as fromRoot from '../reducer';
import * as fromWizard from '../../../wizard/reducer';
import { SharedModule } from '../../../shared/shared.module';
import { ActivatedRouteStub } from '../../../../testing/activated-route.stub';
import { FileBackedHttpMetadataProviderEditor } from '../model';
import { ProviderEditorNavComponent } from '../component/provider-editor-nav.component';
import { NgbModalStub } from '../../../../testing/modal.stub';
import { MetadataProvider } from '../../domain/model';
import { of } from 'rxjs';

@Component({
template: `
Expand All @@ -32,6 +35,7 @@ describe('Provider Edit Component', () => {
let router: Router;
let activatedRoute: ActivatedRouteStub = new ActivatedRouteStub();
let child: ActivatedRouteStub = new ActivatedRouteStub();
let modal: NgbModal;
child.testParamMap = { form: 'common' };
activatedRoute.firstChild = child;

Expand Down Expand Up @@ -60,13 +64,15 @@ describe('Provider Edit Component', () => {
ProviderEditorNavComponent
],
providers: [
{ provide: NgbModal, useClass: NgbModalStub },
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: APP_BASE_HREF, useValue: '/' }
]
}).compileComponents();

store = TestBed.get(Store);
router = TestBed.get(Router);
modal = TestBed.get(NgbModal);
spyOn(store, 'dispatch');

fixture = TestBed.createComponent(TestHostComponent);
Expand Down Expand Up @@ -104,8 +110,46 @@ describe('Provider Edit Component', () => {
describe('cancel method', () => {
it('should route to the metadata manager', () => {
spyOn(router, 'navigate');
spyOn(app, 'clear');
app.cancel();
expect(router.navigate).toHaveBeenCalled();
expect(app.clear).toHaveBeenCalled();
});
});

describe('clear method', () => {
it('should dispatch actions to clear the reducer state', () => {
app.clear();
expect(store.dispatch).toHaveBeenCalled();
});
});

describe('canDeactivate method', () => {
it('should check if the current route is another edit page', (done) => {
let route = new ActivatedRouteStub(),
snapshot = route.snapshot;
let result = app.canDeactivate(null, { url: 'edit', root: null }, { url: 'edit', root: null });
result.subscribe(can => {
expect(can).toBe(true);
done();
});
fixture.detectChanges();
});

it('should open a modal', (done) => {
app.latest = <MetadataProvider>{ name: 'bar' };
spyOn(store, 'select').and.returnValue(of(false));
spyOn(modal, 'open').and.returnValue({ result: Promise.resolve('closed') });
fixture.detectChanges();
let route = new ActivatedRouteStub(),
snapshot = route.snapshot;
let result = app.canDeactivate(null, { url: 'edit', root: null }, { url: 'foo', root: null });
result.subscribe(can => {
expect(can).toBe(false);
expect(modal.open).toHaveBeenCalled();
done();
});
fixture.detectChanges();
});
});
});
37 changes: 33 additions & 4 deletions ui/src/app/metadata/provider/container/provider-edit.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { Router, ActivatedRoute, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { skipWhile, map, combineLatest } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import * as fromWizard from '../../../wizard/reducer';
Expand All @@ -12,14 +12,18 @@ import { ClearProvider } from '../action/entity.action';
import { Wizard } from '../../../wizard/model';
import { UpdateProviderRequest } from '../action/collection.action';
import { NAV_FORMATS } from '../component/provider-editor-nav.component';
import { NgbModal } from '../../../../../node_modules/@ng-bootstrap/ng-bootstrap';
import { UnsavedDialogComponent } from '../../resolver/component/unsaved-dialog.component';
import { UnsavedProviderComponent } from '../component/unsaved-provider.dialog';
import { CanComponentDeactivate } from '../../../core/service/can-deactivate.guard';

@Component({
selector: 'provider-edit',
templateUrl: './provider-edit.component.html',
styleUrls: []
})

export class ProviderEditComponent implements OnDestroy {
export class ProviderEditComponent implements OnDestroy, CanComponentDeactivate {

provider$: Observable<MetadataProvider>;
definition$: Observable<Wizard<MetadataProvider>>;
Expand All @@ -36,7 +40,8 @@ export class ProviderEditComponent implements OnDestroy {
constructor(
private store: Store<fromProvider.ProviderState>,
private router: Router,
private route: ActivatedRoute
private route: ActivatedRoute,
private modalService: NgbModal
) {
this.provider$ = this.store.select(fromProvider.getSelectedProvider).pipe(skipWhile(d => !d));
this.definition$ = this.store.select(fromWizard.getWizardDefinition).pipe(skipWhile(d => !d));
Expand Down Expand Up @@ -77,6 +82,10 @@ export class ProviderEditComponent implements OnDestroy {
}

ngOnDestroy() {
this.clear();
}

clear(): void {
this.store.dispatch(new ClearProvider());
this.store.dispatch(new ClearWizard());
this.store.dispatch(new ClearEditor());
Expand All @@ -87,7 +96,27 @@ export class ProviderEditComponent implements OnDestroy {
}

cancel(): void {
this.clear();
this.router.navigate(['metadata', 'manager', 'providers']);
}

canDeactivate(
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
): Observable<boolean> {
if (nextState.url.match('edit')) { return of(true); }
if (Object.keys({ ...this.latest }).length > 0) {
let modal = this.modalService.open(UnsavedProviderComponent);
modal.result.then(
() => {
this.clear();
this.router.navigate([nextState.url]);
},
() => console.warn('denied')
);
}
return this.store.select(fromProvider.getEntityIsSaved);
}
}

8 changes: 6 additions & 2 deletions ui/src/app/metadata/provider/provider.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { EntityEffects } from './effect/entity.effect';
import { ProviderFilterListComponent } from './container/provider-filter-list.component';

import { ProviderEditorNavComponent } from './component/provider-editor-nav.component';
import { UnsavedProviderComponent } from './component/unsaved-provider.dialog';

@NgModule({
declarations: [
Expand All @@ -40,9 +41,12 @@ import { ProviderEditorNavComponent } from './component/provider-editor-nav.comp
ProviderSelectComponent,
ProviderFilterListComponent,
SummaryPropertyComponent,
ProviderEditorNavComponent
ProviderEditorNavComponent,
UnsavedProviderComponent
],
entryComponents: [
UnsavedProviderComponent
],
entryComponents: [],
imports: [
ReactiveFormsModule,
CommonModule,
Expand Down
7 changes: 5 additions & 2 deletions ui/src/app/metadata/provider/provider.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ProviderFilterListComponent } from './container/provider-filter-list.co
import { NewFilterComponent } from '../filter/container/new-filter.component';
import { FilterComponent } from '../filter/container/filter.component';
import { EditFilterComponent } from '../filter/container/edit-filter.component';
import { CanDeactivateGuard } from '../../core/service/can-deactivate.guard';

export const ProviderRoutes: Routes = [
{
Expand Down Expand Up @@ -44,6 +45,9 @@ export const ProviderRoutes: Routes = [
path: ':form',
component: ProviderEditStepComponent
}
],
canDeactivate: [
CanDeactivateGuard
]
},
{
Expand All @@ -61,8 +65,7 @@ export const ProviderRoutes: Routes = [
children: [
{
path: 'edit',
component: EditFilterComponent,
canDeactivate: []
component: EditFilterComponent
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/metadata/provider/reducer/entity.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function reducer(state = initialState, action: EntityActionUnion): Entity
}
}

export const isEntitySaved = (state: EntityState) => !Object.keys(state.changes).length && !state.saving;
export const isEntitySaved = (state: EntityState) => state.changes ? !Object.keys(state.changes).length && !state.saving : true;
export const getEntityChanges = (state: EntityState) => state.changes;
export const isEditorSaving = (state: EntityState) => state.saving;
export const getUpdatedEntity = (state: EntityState) => state.changes;
1 change: 0 additions & 1 deletion ui/src/app/metadata/resolver/container/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export class EditorComponent implements OnInit, OnDestroy {
);

this.providerName$ = this.resolver$.pipe(map(p => p.serviceProviderName));
this.changes$ = this.store.select(fromResolver.getEditorChanges);
this.editorIndex$ = this.route.params.pipe(map(params => Number(params.index)));
this.currentPage$ = this.editorIndex$.pipe(map(index => EditorDef.find(r => r.index === index)));
this.editor = EditorDef;
Expand Down
Loading

0 comments on commit 10c1241

Please sign in to comment.