From f6f34f69a8e9048f39f80346a26a60e37eef1337 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 8 Aug 2018 15:12:34 -0400 Subject: [PATCH 1/8] SHIBUI-734: DELETE filters --- .../controller/MetadataFiltersController.java | 87 +++++++++++++------ 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java index 7d873afa6..89a33ea98 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java @@ -14,6 +14,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -21,12 +23,15 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.Stream; + +import static org.springframework.http.HttpStatus.NOT_FOUND; @RestController @RequestMapping("/api/MetadataResolvers/{metadataResolverId}") @@ -40,33 +45,36 @@ public class MetadataFiltersController { @Autowired private MetadataResolverService metadataResolverService; + private static final Supplier HTTP_404_CLIENT_ERROR_EXCEPTION = () -> new HttpClientErrorException(NOT_FOUND); + + @ExceptionHandler + public ResponseEntity notFoundHandler(HttpClientErrorException ex) { + if(ex.getStatusCode() == NOT_FOUND) { + return ResponseEntity.notFound().build(); + } + throw ex; + } + @GetMapping("/Filters") @Transactional(readOnly = true) public ResponseEntity getAll(@PathVariable String metadataResolverId) { - MetadataResolver resolver = repository.findByResourceId(metadataResolverId); - if(resolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); + resolver.convertFiltersIntoTransientRepresentationIfNecessary(); return ResponseEntity.ok(resolver.getMetadataFilters()); } @GetMapping("/Filters/{resourceId}") + @Transactional(readOnly = true) public ResponseEntity getOne(@PathVariable String metadataResolverId, @PathVariable String resourceId) { - MetadataResolver resolver = repository.findByResourceId(metadataResolverId); - if(resolver == null) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(resolver.getMetadataFilters().stream() - .filter(f -> f.getResourceId().equals(resourceId)) - .collect(Collectors.toList()).get(0)); + MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); + resolver.convertFiltersIntoTransientRepresentationIfNecessary(); + return ResponseEntity.ok(findFilterOrThrowHttp404(resolver, resourceId)); } @PostMapping("/Filters") + @Transactional public ResponseEntity create(@PathVariable String metadataResolverId, @RequestBody MetadataFilter createdFilter) { - MetadataResolver metadataResolver = repository.findByResourceId(metadataResolverId); - if(metadataResolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver metadataResolver = findResolverOrThrowHttp404(metadataResolverId); metadataResolver.getMetadataFilters().add(createdFilter); //convert before saving into database @@ -79,7 +87,7 @@ public ResponseEntity create(@PathVariable String metadataResolverId, @Reques metadataResolverService.reloadFilters(persistedMr.getName()); MetadataFilter persistedFilter = - convertIntoTransientRepresentationIfNecessary(persistedMr.getMetadataFilters().stream(), createdFilter.getResourceId()); + convertIntoTransientRepresentationIfNecessary(persistedMr, createdFilter.getResourceId()); return ResponseEntity .created(getResourceUriFor(persistedMr, createdFilter.getResourceId())) @@ -88,14 +96,12 @@ public ResponseEntity create(@PathVariable String metadataResolverId, @Reques } @PutMapping("/Filters/{resourceId}") + @Transactional public ResponseEntity update(@PathVariable String metadataResolverId, @PathVariable String resourceId, @RequestBody MetadataFilter updatedFilter) { - MetadataResolver metadataResolver = repository.findByResourceId(metadataResolverId); - if(metadataResolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver metadataResolver = findResolverOrThrowHttp404(metadataResolverId); if (!resourceId.equals(updatedFilter.getResourceId())) { return new ResponseEntity(HttpStatus.CONFLICT); @@ -132,18 +138,49 @@ public ResponseEntity update(@PathVariable String metadataResolverId, metadataResolverService.reloadFilters(persistedMr.getName()); MetadataFilter persistedFilter = - convertIntoTransientRepresentationIfNecessary(persistedMr.getMetadataFilters().stream(), updatedFilter.getResourceId()); + convertIntoTransientRepresentationIfNecessary(persistedMr, updatedFilter.getResourceId()); persistedFilter.setVersion(persistedFilter.hashCode()); return ResponseEntity.ok().body(persistedFilter); } - private MetadataFilter convertIntoTransientRepresentationIfNecessary(Stream filters, final String filterResourceId) { - MetadataFilter persistedFilter = filters + @DeleteMapping("/Filters/{resourceId}") + @Transactional + public ResponseEntity delete(@PathVariable String metadataResolverId, + @PathVariable String resourceId) { + + MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); + boolean removed = resolver.getMetadataFilters().removeIf(f -> f.getResourceId().equals(resourceId)); + if(!removed) { + throw HTTP_404_CLIENT_ERROR_EXCEPTION.get(); + } + repository.save(resolver); + MetadataResolver persistedMr = repository.findByResourceId(metadataResolverId); + + //TODO: do we need to reload filters here?!? + //metadataResolverService.reloadFilters(persistedMr.getName()); + + return ResponseEntity.noContent().build(); + } + + private MetadataResolver findResolverOrThrowHttp404(String resolverResourceId) { + MetadataResolver resolver = repository.findByResourceId(resolverResourceId); + if(resolver == null) { + throw HTTP_404_CLIENT_ERROR_EXCEPTION.get(); + } + return resolver; + } + + private MetadataFilter findFilterOrThrowHttp404(MetadataResolver resolver, String filterResourceId) { + return resolver.getMetadataFilters().stream() .filter(f -> f.getResourceId().equals(filterResourceId)) - .collect(Collectors.toList()).get(0); + .findFirst() + .orElseThrow(HTTP_404_CLIENT_ERROR_EXCEPTION); + } + private MetadataFilter convertIntoTransientRepresentationIfNecessary(MetadataResolver resolver, final String filterResourceId) { + MetadataFilter persistedFilter = findFilterOrThrowHttp404(resolver, filterResourceId); //convert before saving into database if(persistedFilter instanceof EntityAttributesFilter) { EntityAttributesFilter.class.cast(persistedFilter).intoTransientRepresentation(); From 447751f425c4a81b24ee69c2307df46c7b72a098 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 15 Aug 2018 16:00:50 -0400 Subject: [PATCH 2/8] Add test for delete filter --- .../controller/MetadataFiltersController.java | 42 ++++++++++--------- ...taFiltersControllerIntegrationTests.groovy | 23 ++++++++++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java index 64e7a0fe6..605f948a9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java @@ -15,6 +15,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -47,33 +48,31 @@ public class MetadataFiltersController { private static final Supplier HTTP_404_CLIENT_ERROR_EXCEPTION = () -> new HttpClientErrorException(NOT_FOUND); + @ExceptionHandler + public ResponseEntity notFoundHandler(HttpClientErrorException ex) { + if(ex.getStatusCode() == NOT_FOUND) { + return ResponseEntity.notFound().build(); + } + throw ex; + } + @GetMapping("/Filters") @Transactional(readOnly = true) public ResponseEntity getAll(@PathVariable String metadataResolverId) { - MetadataResolver resolver = repository.findByResourceId(metadataResolverId); - if(resolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); return ResponseEntity.ok(resolver.getMetadataFilters()); } @GetMapping("/Filters/{resourceId}") + @Transactional(readOnly = true) public ResponseEntity getOne(@PathVariable String metadataResolverId, @PathVariable String resourceId) { - MetadataResolver resolver = repository.findByResourceId(metadataResolverId); - if(resolver == null) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(resolver.getMetadataFilters().stream() - .filter(f -> f.getResourceId().equals(resourceId)) - .collect(Collectors.toList()).get(0)); + MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); + return ResponseEntity.ok(findFilterOrThrowHttp404(resolver, resourceId)); } @PostMapping("/Filters") public ResponseEntity create(@PathVariable String metadataResolverId, @RequestBody MetadataFilter createdFilter) { - MetadataResolver metadataResolver = repository.findByResourceId(metadataResolverId); - if(metadataResolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver metadataResolver = findResolverOrThrowHttp404(metadataResolverId); metadataResolver.getMetadataFilters().add(createdFilter); MetadataResolver persistedMr = repository.save(metadataResolver); @@ -85,7 +84,6 @@ public ResponseEntity create(@PathVariable String metadataResolverId, @Reques return ResponseEntity .created(getResourceUriFor(persistedMr, createdFilter.getResourceId())) .body(persistedFilter); - } @PutMapping("/Filters/{resourceId}") @@ -97,10 +95,7 @@ public ResponseEntity update(@PathVariable String metadataResolverId, return ResponseEntity.notFound().build(); } - MetadataResolver metadataResolver = repository.findByResourceId(metadataResolverId); - if(metadataResolver == null) { - return ResponseEntity.notFound().build(); - } + MetadataResolver metadataResolver = findResolverOrThrowHttp404(metadataResolverId); // check to make sure that the relationship exists if (!metadataResolver.getMetadataFilters().contains(filterTobeUpdated)) { @@ -155,6 +150,13 @@ private MetadataResolver findResolverOrThrowHttp404(String resolverResourceId) { return resolver; } + private MetadataFilter findFilterOrThrowHttp404(MetadataResolver resolver, String filterResourceId) { + return resolver.getMetadataFilters().stream() + .filter(f -> f.getResourceId().equals(filterResourceId)) + .findFirst() + .orElseThrow(HTTP_404_CLIENT_ERROR_EXCEPTION); + } + private MetadataFilter newlyPersistedFilter(Stream filters, final String filterResourceId) { MetadataFilter persistedFilter = filters .filter(f -> f.getResourceId().equals(filterResourceId)) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy index 557fdf56e..e65a7e963 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy @@ -103,6 +103,29 @@ class MetadataFiltersControllerIntegrationTests extends Specification { updatedResultFromPUT.statusCode.value() == 200 } + def "DELETE Filter"() { + given: 'MetadataResolver with attached filter is available in data store' + def resolver = generator.buildRandomMetadataResolverOfType('FileBacked') + resolver.metadataFilters << generator.entityAttributesFilter() + def filterResourceId = resolver.metadataFilters[0].resourceId + def resolverResourceId = resolver.resourceId + metadataResolverRepository.save(resolver) + + + when: 'GET request is made with resource Id matching the existing filter' + def result = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId/Filters/$filterResourceId", String) + + then: + result.statusCode.value() == 200 + + and: 'DELETE call is made and then GET call is made for the just deleted resource' + restTemplate.delete("$BASE_URI/$resolverResourceId/Filters/$filterResourceId") + def GETResultAfterDelete = this.restTemplate.getForEntity("$BASE_URI/$resolverResourceId/Filters/$filterResourceId", String) + + then: 'The deleted resource is gone' + GETResultAfterDelete.statusCode.value() == 404 + } + private HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) } From 4b05e15f8d535603da13b7c7ff7e0f4c863abfcf Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 16 Aug 2018 10:08:41 -0700 Subject: [PATCH 3/8] SHIBUI-770 Implemented validation for duplicate entity id entries on filters --- .../filter/effect/collection.effect.ts | 9 +++++ .../provider/effect/collection.effect.ts | 33 +++++++++++++++---- .../app/metadata/provider/provider.module.ts | 2 ++ .../filter-target.component.html | 4 ++- .../filter-target/filter-target.component.ts | 14 ++++++-- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index cc41754c2..bbef8c89b 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -14,6 +14,7 @@ import { MetadataFilter } from '../../domain/model'; import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilterEntity } from '../../domain/entity/filter/entity-attributes-filter'; import { MetadataFilterService } from '../../domain/service/filter.service'; +import { SelectProviderRequest } from '../../provider/action/collection.action'; /* istanbul ignore next */ @Injectable() @@ -96,6 +97,14 @@ export class FilterCollectionEffects { ); }) ); + @Effect() + updateFilterSuccessReloadProvider$ = this.actions$.pipe( + ofType(FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS), + map(action => action.payload), + withLatestFrom(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + map(([filter, providerId]) => new SelectProviderRequest(providerId)) + ); + @Effect({ dispatch: false }) updateFilterSuccessRedirect$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS), diff --git a/ui/src/app/metadata/provider/effect/collection.effect.ts b/ui/src/app/metadata/provider/effect/collection.effect.ts index 55528b395..73ea1937b 100644 --- a/ui/src/app/metadata/provider/effect/collection.effect.ts +++ b/ui/src/app/metadata/provider/effect/collection.effect.ts @@ -6,7 +6,6 @@ import { Router } from '@angular/router'; import { of } from 'rxjs'; import { map, catchError, switchMap, tap, withLatestFrom, debounceTime } from 'rxjs/operators'; import { - ProviderCollectionActionsUnion, ProviderCollectionActionTypes, AddProviderRequest, AddProviderSuccess, @@ -32,14 +31,32 @@ import { import { MetadataProviderService } from '../../domain/service/provider.service'; import * as fromProvider from '../reducer'; import * as fromRoot from '../../../app.reducer'; -import { AddFilterSuccess, FilterCollectionActionTypes } from '../../filter/action/collection.action'; -import { debounce } from '../../../../../node_modules/rxjs-compat/operator/debounce'; +import { ClearProvider, ResetChanges } from '../action/entity.action'; +import { ShowContentionAction } from '../../../contention/action/contention.action'; +import { ContentionService } from '../../../contention/service/contention.service'; +import { MetadataProvider } from '../../domain/model'; /* istanbul ignore next */ @Injectable() export class CollectionEffects { + @Effect() + openContention$ = this.actions$.pipe( + ofType(ProviderCollectionActionTypes.UPDATE_PROVIDER_FAIL), + map(action => action.payload), + withLatestFrom(this.store.select(fromProvider.getSelectedProvider)), + switchMap(([changes, current]) => + this.providerService.find(current.resourceId).pipe( + map(data => new ShowContentionAction(this.contentionService.getContention(current, changes, data, { + resolve: (obj) => this.store.dispatch(new UpdateProviderRequest({ ...obj })), + reject: (obj) => this.store.dispatch(new ResetChanges()) + }))) + ) + ) + ); + + @Effect() loadProviders$ = this.actions$.pipe( ofType(ProviderCollectionActionTypes.LOAD_PROVIDER_REQUEST), @@ -100,7 +117,7 @@ export class CollectionEffects { .update(provider) .pipe( map(p => new UpdateProviderSuccess({id: p.id, changes: p})), - catchError((e) => of(new UpdateProviderFail(e))) + catchError((e) => of(new UpdateProviderFail(provider))) ) ) ); @@ -116,7 +133,10 @@ export class CollectionEffects { updateProviderSuccessRedirect$ = this.actions$.pipe( ofType(ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS), map(action => action.payload), - tap(provider => this.router.navigate(['metadata', 'manager', 'providers'])) + tap(provider => { + this.store.dispatch(new ClearProvider()); + this.router.navigate(['metadata', 'manager', 'providers']); + }) ); @Effect() @@ -210,6 +230,7 @@ export class CollectionEffects { private actions$: Actions, private router: Router, private store: Store, - private providerService: MetadataProviderService + private providerService: MetadataProviderService, + private contentionService: ContentionService ) { } } diff --git a/ui/src/app/metadata/provider/provider.module.ts b/ui/src/app/metadata/provider/provider.module.ts index 95d037256..80360c517 100644 --- a/ui/src/app/metadata/provider/provider.module.ts +++ b/ui/src/app/metadata/provider/provider.module.ts @@ -29,6 +29,7 @@ import { ProviderFilterListComponent } from './container/provider-filter-list.co import { ProviderEditorNavComponent } from './component/provider-editor-nav.component'; import { UnsavedProviderComponent } from './component/unsaved-provider.dialog'; +import { ContentionModule } from '../../contention/contention.module'; @NgModule({ declarations: [ @@ -55,6 +56,7 @@ import { UnsavedProviderComponent } from './component/unsaved-provider.dialog'; SharedModule, FormModule, RouterModule, + ContentionModule, NgbDropdownModule ], exports: [] diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html index 482803777..5ddb9aeda 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html @@ -32,7 +32,9 @@ [matches]="ids$ | async" (keydown.enter)="onSelectValue(search.value)"> - You must add at least one entity id target. + + You must add at least one entity id target and they must each be unique. +

diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts index ab810dc32..a93963678 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, AfterViewInit } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { FormControl, Validators, AbstractControl, ValidatorFn } from '@angular/forms'; import { ObjectWidget } from 'ngx-schema-form'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -25,7 +25,9 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af search: FormControl = new FormControl( '', [], - [EntityValidators.existsInCollection(this.store.select(fromFilters.getEntityCollection))] + [ + EntityValidators.existsInCollection(this.store.select(fromFilters.getEntityCollection)) + ] ); script: FormControl = new FormControl( @@ -59,6 +61,14 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af ngAfterViewInit(): void { super.ngAfterViewInit(); this.script.setValue(this.targets[0]); + + this.search.setValidators(this.unique()); + } + + unique(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + return this.targets.indexOf(control.value) > -1 ? { unique: true } : null; + }; } searchEntityIds(term: string): void { From 8c4945d377b47d2d620bef81a397b71fd9d71fe9 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Thu, 16 Aug 2018 15:19:38 -0400 Subject: [PATCH 4/8] Add TODO note about manipulating filters directly via FilterRepository --- .../admin/ui/controller/MetadataFiltersController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java index 605f948a9..2ec5f01ab 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java @@ -130,6 +130,7 @@ public ResponseEntity delete(@PathVariable String metadataResolverId, @PathVariable String resourceId) { MetadataResolver resolver = findResolverOrThrowHttp404(metadataResolverId); + //TODO: consider implementing delete of filter directly from RDBMS via FilterRepository boolean removed = resolver.getMetadataFilters().removeIf(f -> f.getResourceId().equals(resourceId)); if(!removed) { throw HTTP_404_CLIENT_ERROR_EXCEPTION.get(); From e6f851098f6287e255db4fb3d2ec74ef7dbd69f1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 17 Aug 2018 16:04:22 +0000 Subject: [PATCH 5/8] Merged in feature/SHIBUI-730 (pull request #165) SHIBUI-730 Implemented delete functionality for filters Approved-by: Shibui Jenkins --- .../metadata/domain/service/filter.service.ts | 6 ++++-- .../filter/action/collection.action.ts | 6 +++--- .../filter/effect/collection.effect.ts | 21 +++++++++++++++++++ .../filter/reducer/collection.reducer.spec.ts | 10 ++++++++- .../filter/reducer/collection.reducer.ts | 15 +++++++++++++ ui/src/app/metadata/filter/reducer/index.ts | 2 +- .../provider-filter-list.component.html | 6 ++---- .../provider-filter-list.component.spec.ts | 7 +++++++ .../provider-filter-list.component.ts | 6 +++++- 9 files changed, 67 insertions(+), 12 deletions(-) diff --git a/ui/src/app/metadata/domain/service/filter.service.ts b/ui/src/app/metadata/domain/service/filter.service.ts index 7334ee301..92dabd797 100644 --- a/ui/src/app/metadata/domain/service/filter.service.ts +++ b/ui/src/app/metadata/domain/service/filter.service.ts @@ -18,7 +18,6 @@ export class MetadataFilterService { } find(providerId: string, filterId: string): Observable { - // console.log(id); return this.http.get(`${this.base}${this.endpoint}/${providerId}/Filters/${ filterId }`); } @@ -27,7 +26,10 @@ export class MetadataFilterService { } save(providerId: string, filter: MetadataFilter): Observable { - console.log(providerId, filter); return this.http.post(`${this.base}${this.endpoint}/${providerId}/Filters`, filter); } + + remove(providerId: string, filterId: string): Observable { + return this.http.delete(`${this.base}${this.endpoint}/${providerId}/Filters/${ filterId }`); + } } diff --git a/ui/src/app/metadata/filter/action/collection.action.ts b/ui/src/app/metadata/filter/action/collection.action.ts index c93c563d8..e2b38793d 100644 --- a/ui/src/app/metadata/filter/action/collection.action.ts +++ b/ui/src/app/metadata/filter/action/collection.action.ts @@ -99,19 +99,19 @@ export class AddFilterFail implements Action { export class RemoveFilterRequest implements Action { readonly type = FilterCollectionActionTypes.REMOVE_FILTER_REQUEST; - constructor(public payload: MetadataFilter) { } + constructor(public payload: string) { } } export class RemoveFilterSuccess implements Action { readonly type = FilterCollectionActionTypes.REMOVE_FILTER_SUCCESS; - constructor(public payload: MetadataFilter) { } + constructor(public payload: string) { } } export class RemoveFilterFail implements Action { readonly type = FilterCollectionActionTypes.REMOVE_FILTER_FAIL; - constructor(public payload: MetadataFilter) { } + constructor(public error: Error) { } } export type FilterCollectionActionsUnion = diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index bbef8c89b..779bb088f 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -113,6 +113,27 @@ export class FilterCollectionEffects { tap(([filter, provider]) => this.router.navigate(['/', 'metadata', 'provider', provider, 'filters'])) ); + @Effect() + removeFilterRequest$ = this.actions$.pipe( + ofType(FilterCollectionActionTypes.REMOVE_FILTER_REQUEST), + map(action => action.payload), + withLatestFrom(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + switchMap(([filterId, providerId]) => + this.filterService.remove(providerId, filterId).pipe( + map(removed => new actions.RemoveFilterSuccess(removed)), + catchError(err => of(new actions.RemoveFilterFail(err))) + ) + ) + ); + + @Effect() + removeFilterSuccess$ = this.actions$.pipe( + ofType(FilterCollectionActionTypes.REMOVE_FILTER_SUCCESS), + map(action => action.payload), + withLatestFrom(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + map(([filter, providerId]) => new actions.LoadFilterRequest(providerId)) + ); + constructor( private actions$: Actions, private router: Router, diff --git a/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts b/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts index dfe5fc107..8ff807401 100644 --- a/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts +++ b/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts @@ -10,7 +10,8 @@ import { UpdateFilterRequest, AddFilterSuccess, AddFilterFail, - UpdateFilterFail + UpdateFilterFail, + RemoveFilterFail } from '../action/collection.action'; import { EntityAttributesFilterEntity } from '../../domain/entity/filter/entity-attributes-filter'; @@ -93,6 +94,13 @@ describe('Filter Reducer', () => { }); }); + describe(`${FilterCollectionActionTypes.REMOVE_FILTER_FAIL}`, () => { + it('should set saving to false', () => { + const action = new RemoveFilterFail(new Error('foo')); + expect(reducer(snapshot, action).saving).toBe(false); + }); + }); + describe(`${FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS}`, () => { it('should update the filter in the collection', () => { spyOn(fromFilter.adapter, 'updateOne').and.callThrough(); diff --git a/ui/src/app/metadata/filter/reducer/collection.reducer.ts b/ui/src/app/metadata/filter/reducer/collection.reducer.ts index 35074b696..1f3788c54 100644 --- a/ui/src/app/metadata/filter/reducer/collection.reducer.ts +++ b/ui/src/app/metadata/filter/reducer/collection.reducer.ts @@ -58,6 +58,7 @@ export function reducer(state = initialState, action: FilterCollectionActionsUni case FilterCollectionActionTypes.ADD_FILTER_SUCCESS: case FilterCollectionActionTypes.ADD_FILTER_FAIL: + case FilterCollectionActionTypes.REMOVE_FILTER_FAIL: case FilterCollectionActionTypes.UPDATE_FILTER_FAIL: { return { ...state, @@ -65,6 +66,20 @@ export function reducer(state = initialState, action: FilterCollectionActionsUni }; } + case FilterCollectionActionTypes.REMOVE_FILTER_SUCCESS: { + return adapter.removeOne(action.payload, { + ...state, + saving: false + }); + } + + case FilterCollectionActionTypes.REMOVE_FILTER_REQUEST: { + return { + ...state, + saving: true + }; + } + case FilterCollectionActionTypes.SELECT_FILTER_REQUEST: { return { ...state, diff --git a/ui/src/app/metadata/filter/reducer/index.ts b/ui/src/app/metadata/filter/reducer/index.ts index 5d9a4ecbe..60cbe4b34 100644 --- a/ui/src/app/metadata/filter/reducer/index.ts +++ b/ui/src/app/metadata/filter/reducer/index.ts @@ -52,7 +52,7 @@ export const getAllFilters = createSelector(getCollectionState, fromCollection.s export const getCollectionSaving = createSelector(getCollectionState, fromCollection.getIsSaving); export const notAddtlFilters = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; -export const filterTypeFn = filters => filters.filter(f => notAddtlFilters.indexOf(f['@type']) === -1); +export const filterTypeFn = filters => [...filters.filter(f => notAddtlFilters.indexOf(f['@type']) === -1)]; export const getAdditionalFilters = createSelector(getAllFilters, filterTypeFn); diff --git a/ui/src/app/metadata/provider/container/provider-filter-list.component.html b/ui/src/app/metadata/provider/container/provider-filter-list.component.html index 53e99847c..3f29393c0 100644 --- a/ui/src/app/metadata/provider/container/provider-filter-list.component.html +++ b/ui/src/app/metadata/provider/container/provider-filter-list.component.html @@ -40,7 +40,7 @@ Filter Type Enabled? Edit - + Delete @@ -79,14 +79,12 @@ Edit - diff --git a/ui/src/app/metadata/provider/container/provider-filter-list.component.spec.ts b/ui/src/app/metadata/provider/container/provider-filter-list.component.spec.ts index f24673419..7b850fc33 100644 --- a/ui/src/app/metadata/provider/container/provider-filter-list.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-filter-list.component.spec.ts @@ -59,4 +59,11 @@ describe('Provider Filter List Component', () => { it('should instantiate the component', async(() => { expect(app).toBeTruthy(); })); + + describe('remove method', () => { + it('should dispatch an action to the store', () => { + app.remove('foo'); + expect(store.dispatch).toHaveBeenCalled(); + }); + }); }); diff --git a/ui/src/app/metadata/provider/container/provider-filter-list.component.ts b/ui/src/app/metadata/provider/container/provider-filter-list.component.ts index 92c346505..3513590cd 100644 --- a/ui/src/app/metadata/provider/container/provider-filter-list.component.ts +++ b/ui/src/app/metadata/provider/container/provider-filter-list.component.ts @@ -7,7 +7,7 @@ import * as fromFilter from '../../filter/reducer'; import { MetadataFilter, MetadataProvider } from '../../domain/model'; import { NAV_FORMATS } from '../component/provider-editor-nav.component'; import { SetIndex } from '../../../wizard/action/wizard.action'; -import { UpdateFilterRequest, LoadFilterRequest } from '../../filter/action/collection.action'; +import { UpdateFilterRequest, LoadFilterRequest, RemoveFilterRequest } from '../../filter/action/collection.action'; @Component({ selector: 'provider-filter-list', @@ -44,6 +44,10 @@ export class ProviderFilterListComponent implements OnDestroy { this.store.dispatch(new UpdateFilterRequest({ ...filter, filterEnabled: !filter.filterEnabled })); } + remove(id: string): void { + this.store.dispatch(new RemoveFilterRequest(id)); + } + ngOnDestroy(): void { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); From 6232ce8799b4b499d8e7d164770e6d0584d81f0a Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 17 Aug 2018 17:29:49 +0000 Subject: [PATCH 6/8] Merged in bugfix/SHIBUI-646 (pull request #166) SHIBUI-646 Removed show more link * SHIBUI-646 Removed show more link * SHIBUI-646 Fixed issue with loading provider before filter Approved-by: Shibui Jenkins Approved-by: Ryan Mathis --- .../container/provider-filter-list.component.ts | 4 +++- .../provider/container/provider-select.component.html | 4 +++- .../shared/autocomplete/autocomplete.component.html | 10 ---------- .../app/shared/autocomplete/autocomplete.component.ts | 9 +-------- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/ui/src/app/metadata/provider/container/provider-filter-list.component.ts b/ui/src/app/metadata/provider/container/provider-filter-list.component.ts index 3513590cd..0b1fee37b 100644 --- a/ui/src/app/metadata/provider/container/provider-filter-list.component.ts +++ b/ui/src/app/metadata/provider/container/provider-filter-list.component.ts @@ -30,7 +30,9 @@ export class ProviderFilterListComponent implements OnDestroy { this.filters$ = this.store.select(fromFilter.getAdditionalFilters); this.provider$ = this.store.select(fromProvider.getSelectedProvider).pipe(skipWhile(p => !p)); this.provider$ - .pipe(takeUntil(this.ngUnsubscribe)) + .pipe( + takeUntil(this.ngUnsubscribe) + ) .subscribe(p => { this.store.dispatch(new LoadFilterRequest(p.resourceId)); }); diff --git a/ui/src/app/metadata/provider/container/provider-select.component.html b/ui/src/app/metadata/provider/container/provider-select.component.html index 90c6b6463..5389bd7e9 100644 --- a/ui/src/app/metadata/provider/container/provider-select.component.html +++ b/ui/src/app/metadata/provider/container/provider-select.component.html @@ -1 +1,3 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/ui/src/app/shared/autocomplete/autocomplete.component.html b/ui/src/app/shared/autocomplete/autocomplete.component.html index 8f952f520..bba151e16 100644 --- a/ui/src/app/shared/autocomplete/autocomplete.component.html +++ b/ui/src/app/shared/autocomplete/autocomplete.component.html @@ -50,15 +50,5 @@ - diff --git a/ui/src/app/shared/autocomplete/autocomplete.component.ts b/ui/src/app/shared/autocomplete/autocomplete.component.ts index d6237742a..51fc555fb 100644 --- a/ui/src/app/shared/autocomplete/autocomplete.component.ts +++ b/ui/src/app/shared/autocomplete/autocomplete.component.ts @@ -40,13 +40,12 @@ const INPUT_FIELD_INDEX = -1; } ] }) -export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit, ControlValueAccessor { +export class AutoCompleteComponent implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor { @Input() defaultValue = ''; @Input() matches: string[] = []; @Input() id: string; @Input() autoSelect = false; @Input() noneFoundText = 'No Options Found'; - @Input() showMoreText = 'Show More...'; @Input() limit = 0; @Input() processing = false; @Input() dropdown = false; @@ -119,12 +118,6 @@ export class AutoCompleteComponent implements OnInit, OnDestroy, OnChanges, Afte this.listItems.changes.subscribe((changes) => this.setElementReferences(changes)); } - ngOnChanges(changes: SimpleChanges): void { - if (changes.matches) { - this.showMoreAvailable = !!this.limit && this.matches && this.matches.length >= this.limit; - } - } - writeValue(value: any): void { this.input.setValue(value); } From a6d6a5dbc34a97d1760086ed96071edb927b77bd Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 17 Aug 2018 10:35:17 -0700 Subject: [PATCH 7/8] SHIBUI-760 Added enable checkbox to editor --- .../provider/filebacked-http-common.editor.schema.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/src/assets/schema/provider/filebacked-http-common.editor.schema.json b/ui/src/assets/schema/provider/filebacked-http-common.editor.schema.json index 98545e7e4..2b5d368e4 100644 --- a/ui/src/assets/schema/provider/filebacked-http-common.editor.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-common.editor.schema.json @@ -50,7 +50,8 @@ "type": "section", "fields": [ "name", - "@type" + "@type", + "enabled" ] }, { @@ -96,6 +97,12 @@ } ] }, + "enabled": { + "title": "Enable this service?", + "description": "Enable this service?", + "type": "boolean", + "default": false + }, "xmlId": { "title": "ID", "description": "Identifier for logging, identification for command line reload, etc.", From 390f3bf76fab6e3284e0a0d0b9d4c4ae963c271e Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 20 Aug 2018 09:26:57 -0700 Subject: [PATCH 8/8] SHIBUI-777 Added info icons and required labels --- .../filter-target.component.html | 118 +++++++++++------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html index 5ddb9aeda..5124e7fac 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html @@ -1,7 +1,17 @@
-
+
+ -   -

-
-
- - - - - You must add at least one entity id target and they must each be unique. - - - -

- - Required for Scripts -   - -
- - - - Required for Regex -   - - -
-
- +
+ +
+
+ + + + + You must add at least one entity id target and they must each be unique. + + + +

+ + Required for Scripts +   + +
+ + + + Required for Regex +   + + +
+
+ +
-
-
    +
    + +
    +
    • {{ id }}