diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java
new file mode 100644
index 000000000..fa91aa3e6
--- /dev/null
+++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java
@@ -0,0 +1,18 @@
+package edu.internet2.tier.shibboleth.admin.ui.controller;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @author Bill Smith (wsmith@unicon.net)
+ */
+@AllArgsConstructor
+@Getter
+@Setter
+@ToString
+public class ErrorResponse {
+ private String errorCode;
+ private String errorMessage;
+}
diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java
index ae75b3f6d..5b24f5788 100644
--- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java
+++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java
@@ -1,15 +1,16 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;
+import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidationService;
-import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidator;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
-
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.transaction.annotation.Transactional;
+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;
@@ -19,6 +20,7 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import java.io.IOException;
import java.net.URI;
import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidator.ValidationResult;
@@ -34,6 +36,11 @@ public class MetadataResolversController {
@Autowired
MetadataResolverValidationService metadataResolverValidationService;
+ @ExceptionHandler({InvalidTypeIdException.class, IOException.class, HttpMessageNotReadableException.class})
+ public ResponseEntity> unableToParseJson(Exception ex) {
+ return ResponseEntity.badRequest().body(new ErrorResponse(HttpStatus.BAD_REQUEST.toString(), ex.getMessage()));
+ }
+
@GetMapping("/MetadataResolvers")
@Transactional(readOnly = true)
public ResponseEntity> getAll() {
@@ -56,6 +63,10 @@ public ResponseEntity> getOne(@PathVariable String resourceId) {
@PostMapping("/MetadataResolvers")
@Transactional
public ResponseEntity> create(@RequestBody MetadataResolver newResolver) {
+ if (resolverRepository.findByName(newResolver.getName()) != null) {
+ return ResponseEntity.status(HttpStatus.CONFLICT).build();
+ }
+
//TODO: we are disregarding attached filters if any sent from UI.
//Only deal with filters via filters endpoints?
newResolver.clearAllFilters();
diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java
index 26039b81b..4151faeb6 100644
--- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java
+++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java
@@ -8,7 +8,7 @@
/**
* A facade that aggregates {@link MetadataResolverValidator}s available to call just one of them supporting the type of a given resolver.
- * If no {@link MetadataResolverValidator}s are configured, conciders provided MetadataResolver as valid.
+ * If no {@link MetadataResolverValidator}s are configured, considers provided MetadataResolver as valid.
*
* Uses chain-of-responsibility design pattern
*
@@ -22,7 +22,7 @@ public MetadataResolverValidationService(List> vali
this.validators = validators != null ? validators : new ArrayList<>();
}
- @SuppressWarnings("Uncheked")
+ @SuppressWarnings("Unchecked")
public ValidationResult validateIfNecessary(T metadataResolver) {
Optional> validator =
this.validators
diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy
index 7296a2063..73cf58307 100644
--- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy
+++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy
@@ -12,6 +12,8 @@ import org.apache.lucene.index.IndexWriter
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver
import org.opensaml.saml.metadata.resolver.MetadataResolver
import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
@@ -21,6 +23,7 @@ class TestConfiguration {
final OpenSamlObjects openSamlObjects
final IndexWriter indexWriter
final MetadataResolverRepository metadataResolverRepository
+ final Logger logger = LoggerFactory.getLogger(TestConfiguration.class);
TestConfiguration(final OpenSamlObjects openSamlObjects, final IndexWriter indexWriter, final MetadataResolverRepository metadataResolverRepository) {
this.openSamlObjects =openSamlObjects
diff --git a/backend/src/test/resources/metadata/incommon-short.xml b/backend/src/test/resources/metadata/incommon-short.xml
index c807cf343..e8c214173 100644
--- a/backend/src/test/resources/metadata/incommon-short.xml
+++ b/backend/src/test/resources/metadata/incommon-short.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/ui/README.md b/ui/README.md
index e3fd36108..a24915b12 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -1,6 +1,40 @@
-# Ui
+# Angular
-This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.3.1.
+Angular is a complete framework for UI development, meaning that in addition to rendering a web application in a browser, it also provides additional tools for other important UI considerations such as Internationalization (i18n), Accessibility (a11y), HTML 5 push state routing, an http library for connecting with our REST endpoints, a tool for scaffolding new services, components, pages, etc, and extensive end-to-end and unit testing toolset. In comparison to other frameworks, we rely on fewer 3rd party libraries or frameworks to handle these other concerns. The Angular community provides an extensive documentation website with full API documentation, as well as examples/tutorials. This includes a style-guide with rules for formatting code, which we follow on the SHIB-UI project.Angular is actively maintained by Google and has a large community and ecosystem.
+
+## License
+
+Angular is [licensed](https://angular.io/license) under the MIT license.
+
+## Performance
+
+Angular was designed for mobile from the ground up. Aside from limited processing power, mobile devices have other features and limitations that separate them from traditional computers. Touch interfaces, limited screen real estate, and mobile hardware have all been considered when developing the Angular framework. This has resulted in performance gains in Angular across the board, and makes it competitive with the many other UI frameworks available.
+
+## Typescript
+
+Angular uses [TypeScript](https://www.typescriptlang.org/), a superset of JavaScript that implements many new ESNext features as well as making JavaScript strongly typed. This makes errors and exceptions more likely to happen during compilation, resulting in fewer defects.
+
+By focusing on making the framework easier for computers to process, Angular and TypeScript allow for a much richer development process. Tooling for TypeScript and Angular allows for immediate Angular-specific help and feedback with nearly every IDE and editor. Strong typing enables developers to use more productive development tools and practices like static checking when developing JavaScript applications.
+
+However, developers can still write vanilla JavaScript for Angular that runs without transpilation.
+
+## NGRX
+
+The popular Redux state management system is implemented in SHIB-UI via the framework [NgRx](http://ngrx.github.io/). This provides reliable uni-directional data-flow in the UI which organizes the UI state in a clear and predictable way. One of NgRx's most important features is its simple testability since it is based primarily on pure functions.
+
+# JSON-Schema
+
+For Providers and Filters, the forms in SHIB-UI are built based on the standard [JSON-Schema](http://json-schema.org/). This makes the generation of forms in the editors and wizards of the application dynamic, so that if new properties are added to the specification for a Metadata Provider type, it can be added to the JSON-schema of the application without ever having to re-deploy the javascript code. Rendering of the forms is handled using [ngx-schema-form](https://github.com/makinacorpus/ngx-schema-form), a component library for connecting to a JSON-Schema to render Angular form components, and provides extensibility through support of custom components and validation rules.
+
+# Bootstrap
+
+[Bootstrap](http://getbootstrap.com/) 4 is used for the css framework in SHIB-UI, and provides our base theme, a responsive grid system, consistent styling across all major browsers, and pre-styled components which are connected to Angular using ng-bootstrap, a 3rd party framework.
+
+# Development
+
+## Scaffolding
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.1.
## Development server
diff --git a/ui/src/app/app.routing.ts b/ui/src/app/app.routing.ts
index 3de190469..9903039c9 100644
--- a/ui/src/app/app.routing.ts
+++ b/ui/src/app/app.routing.ts
@@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{ path: '', redirectTo: 'metadata', pathMatch: 'full' },
+ { path: 'dashboard', redirectTo: 'metadata', pathMatch: 'full' },
{
path: 'metadata',
loadChildren: './metadata/metadata.module#MetadataModule'
diff --git a/ui/src/app/metadata/domain/domain.module.ts b/ui/src/app/metadata/domain/domain.module.ts
index 3349342d9..c51bbb260 100644
--- a/ui/src/app/metadata/domain/domain.module.ts
+++ b/ui/src/app/metadata/domain/domain.module.ts
@@ -12,6 +12,7 @@ import { EntityDraftService } from './service/draft.service';
import { MetadataProviderService } from './service/provider.service';
import { EntityEffects } from './effect/entity.effect';
import { PreviewDialogComponent } from './component/preview-dialog.component';
+import { MetadataFilterService } from './service/filter.service';
export const COMPONENTS = [
PreviewDialogComponent
@@ -42,7 +43,8 @@ export class DomainModule {
ListValuesService,
ProviderStatusEmitter,
ProviderValueEmitter,
- MetadataProviderService
+ MetadataProviderService,
+ MetadataFilterService
]
};
}
diff --git a/ui/src/app/metadata/domain/domain.type.ts b/ui/src/app/metadata/domain/domain.type.ts
index 78def16d7..3cda36b83 100644
--- a/ui/src/app/metadata/domain/domain.type.ts
+++ b/ui/src/app/metadata/domain/domain.type.ts
@@ -6,9 +6,11 @@ import {
import {
EntityAttributesFilter,
- FileBackedHttpMetadataResolver,
- FileBackedHttpMetadataProvider
+ FileBackedHttpMetadataResolver
} from './entity';
+import {
+ FileBackedHttpMetadataProvider
+} from './model/providers';
export type Filter =
| EntityAttributesFilter;
diff --git a/ui/src/app/metadata/domain/entity/index.ts b/ui/src/app/metadata/domain/entity/index.ts
index 1adb3735e..648255369 100644
--- a/ui/src/app/metadata/domain/entity/index.ts
+++ b/ui/src/app/metadata/domain/entity/index.ts
@@ -1,3 +1,2 @@
export * from './filter/entity-attributes-filter';
-export * from './provider/file-backed-http-metadata-provider';
export * from './resolver/file-backed-http-metadata-resolver';
diff --git a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts b/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts
deleted file mode 100644
index 70b0245a4..000000000
--- a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { FileBackedHttpMetadataProvider } from './file-backed-http-metadata-provider';
-
-describe('FileBackedHttmMetadataProvider construct', () => {
-
- const config = {
- id: 'foo',
- entityId: 'string',
- serviceProviderName: 'string',
- organization: {
- 'name': 'string',
- 'displayName': 'string',
- 'url': 'string'
- },
- contacts: [
- {
- 'name': 'string',
- 'type': 'string',
- 'emailAddress': 'string'
- }
- ],
- mdui: {
- 'displayName': 'string',
- 'informationUrl': 'string',
- 'privacyStatementUrl': 'string',
- 'logoUrl': 'string',
- 'logoHeight': 100,
- 'logoWidth': 100,
- 'description': 'string'
- },
- securityInfo: {
- 'x509CertificateAvailable': true,
- 'authenticationRequestsSigned': true,
- 'wantAssertionsSigned': true,
- 'x509Certificates': [
- {
- 'name': 'string',
- 'type': 'string',
- 'value': 'string'
- }
- ]
- },
- assertionConsumerServices: [
- {
- 'binding': 'string',
- 'locationUrl': 'string',
- 'makeDefault': true
- }
- ],
- serviceProviderSsoDescriptor: {
- 'protocolSupportEnum': 'string',
- 'nameIdFormats': [
- 'string'
- ]
- },
-
- logoutEndpoints: [
- {
- 'url': 'string',
- 'bindingType': 'string'
- }
- ],
- serviceEnabled: true,
- createdDate: new Date().toDateString(),
- modifiedDate: new Date().toDateString(),
- relyingPartyOverrides: {
- 'signAssertion': true,
- 'dontSignResponse': true,
- 'turnOffEncryption': true,
- 'useSha': true,
- 'ignoreAuthenticationMethod': true,
- 'omitNotBefore': true,
- 'responderId': 'string',
- 'nameIdFormats': [
- 'string'
- ],
- 'authenticationMethods': [
- 'string'
- ]
- },
- attributeRelease: [
- 'eduPersonPrincipalName',
- 'uid',
- 'mail'
- ]
- };
-
- let entity;
-
- beforeEach(() => {
- entity = new FileBackedHttpMetadataProvider(config);
- });
-
- it('should populate its own values', () => {
- Object.keys(config).forEach(key => {
- expect(entity[key]).toEqual(config[key]);
- });
- });
-
- describe('interface methods', () => {
- it('should return a date object from getCreationDate', () => {
- expect(entity.getCreationDate()).toEqual(new Date(config.createdDate));
- });
- });
-});
diff --git a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts b/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts
deleted file mode 100644
index 2f64a98ba..000000000
--- a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {
- MetadataProvider
-} from '../../model';
-
-export class FileBackedHttpMetadataProvider implements MetadataProvider {
- id: string;
- name: string;
- '@type': string;
-
- createdDate?: string;
- modifiedDate?: string;
- version: string;
-
- constructor(descriptor?: Partial) {
- Object.assign(this, descriptor);
- }
-
- getCreationDate(): Date {
- return new Date(this.createdDate);
- }
-}
diff --git a/ui/src/app/metadata/domain/model/metadata-provider.ts b/ui/src/app/metadata/domain/model/metadata-provider.ts
index 3239bd4b0..4dc283368 100644
--- a/ui/src/app/metadata/domain/model/metadata-provider.ts
+++ b/ui/src/app/metadata/domain/model/metadata-provider.ts
@@ -1,16 +1,10 @@
import {
MetadataBase,
- Organization,
- Contact,
- MDUI,
- SecurityInfo,
- SsoService,
- IdpSsoDescriptor,
- LogoutEndpoint,
- RelyingPartyOverrides
} from '../model';
export interface MetadataProvider extends MetadataBase {
name: string;
'@type': string;
+ enabled: boolean;
+ resourceId: string;
}
diff --git a/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts b/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts
new file mode 100644
index 000000000..7d107036b
--- /dev/null
+++ b/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts
@@ -0,0 +1,5 @@
+import { MetadataProvider } from '../metadata-provider';
+
+export interface FileBackedHttpMetadataProvider extends MetadataProvider {
+ metadataFilters: any[];
+}
diff --git a/ui/src/app/metadata/domain/model/providers/index.ts b/ui/src/app/metadata/domain/model/providers/index.ts
new file mode 100644
index 000000000..0cd02d47d
--- /dev/null
+++ b/ui/src/app/metadata/domain/model/providers/index.ts
@@ -0,0 +1 @@
+export * from './file-backed-http-metadata-provider';
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/service/filter.service.spec.ts b/ui/src/app/metadata/domain/service/filter.service.spec.ts
new file mode 100644
index 000000000..1410f6723
--- /dev/null
+++ b/ui/src/app/metadata/domain/service/filter.service.spec.ts
@@ -0,0 +1,74 @@
+import { TestBed, async, inject } from '@angular/core/testing';
+import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
+import { HttpClientModule, HttpRequest } from '@angular/common/http';
+import { MetadataFilterService } from './filter.service';
+import { EntityAttributesFilter } from '../entity';
+
+describe(`Metadata Filter Service`, () => {
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ HttpClientModule,
+ HttpClientTestingModule
+ ],
+ providers: [
+ MetadataFilterService
+ ]
+ });
+ });
+
+ describe('query method', () => {
+ it(`should send an expected GET[] request`, async(inject([MetadataFilterService, HttpTestingController],
+ (service: MetadataFilterService, backend: HttpTestingController) => {
+ service.query().subscribe();
+
+ backend.expectOne((req: HttpRequest) => {
+ return req.url === `${service.base}${service.endpoint}`
+ && req.method === 'GET';
+ }, `GET MetadataResolvers collection`);
+ }
+ )));
+ });
+ describe('find method', () => {
+ it(`should send an expected GET request`, async(inject([MetadataFilterService, HttpTestingController],
+ (service: MetadataFilterService, backend: HttpTestingController) => {
+ const id = 'foo';
+ service.find(id).subscribe();
+
+ backend.expectOne((req: HttpRequest) => {
+ return req.url === `${service.base}${service.endpoint}/${id}`
+ && req.method === 'GET';
+ }, `GET MetadataResolvers collection`);
+ }
+ )));
+ });
+ describe('update method', () => {
+ it(`should send an expected PUT request`, async(inject([MetadataFilterService, HttpTestingController],
+ (service: MetadataFilterService, backend: HttpTestingController) => {
+ const id = 'foo';
+ const filter = new EntityAttributesFilter({ id });
+ service.update(filter).subscribe();
+
+ backend.expectOne((req: HttpRequest) => {
+ return req.url === `${service.base}${service.endpoint}/${id}`
+ && req.method === 'PUT';
+ }, `PUT (update) MetadataResolvers collection`);
+ }
+ )));
+ });
+ describe('save method', () => {
+ it(`should send an expected POST request`, async(inject([MetadataFilterService, HttpTestingController],
+ (service: MetadataFilterService, backend: HttpTestingController) => {
+ const id = 'foo';
+ const filter = new EntityAttributesFilter({ id });
+ service.save(filter).subscribe();
+
+ backend.expectOne((req: HttpRequest) => {
+ return req.url === `${service.base}${service.endpoint}`
+ && req.method === 'POST';
+ }, `POST MetadataResolvers collection`);
+ }
+ )));
+ });
+});
diff --git a/ui/src/app/metadata/domain/service/filter.service.ts b/ui/src/app/metadata/domain/service/filter.service.ts
new file mode 100644
index 000000000..b96907eca
--- /dev/null
+++ b/ui/src/app/metadata/domain/service/filter.service.ts
@@ -0,0 +1,32 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { MetadataFilter } from '../../domain/model';
+
+@Injectable()
+export class MetadataFilterService {
+
+ readonly endpoint = '/MetadataResolvers';
+ readonly base = '/api';
+
+ constructor(
+ private http: HttpClient
+ ) { }
+ query(): Observable {
+ return this.http.get(`${this.base}${this.endpoint}`, {});
+ }
+
+ find(id: string): Observable {
+ // console.log(id);
+ return this.http.get(`${this.base}${this.endpoint}/${id}`);
+ }
+
+ update(filter: MetadataFilter): Observable {
+ return this.http.put(`${this.base}${this.endpoint}/${filter.id}`, filter);
+ }
+
+ save(filter: MetadataFilter): Observable {
+ return this.http.post(`${this.base}${this.endpoint}`, filter);
+ }
+}
diff --git a/ui/src/app/metadata/domain/service/provider.service.spec.ts b/ui/src/app/metadata/domain/service/provider.service.spec.ts
index 814c413ac..356bf5964 100644
--- a/ui/src/app/metadata/domain/service/provider.service.spec.ts
+++ b/ui/src/app/metadata/domain/service/provider.service.spec.ts
@@ -2,7 +2,7 @@ import { TestBed, async, inject } from '@angular/core/testing';
import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpClientModule, HttpRequest } from '@angular/common/http';
import { MetadataProviderService } from './provider.service';
-import { EntityAttributesFilter } from '../entity';
+import { MetadataProvider } from '../model';
describe(`Metadata Provider Service`, () => {
@@ -47,8 +47,8 @@ describe(`Metadata Provider Service`, () => {
it(`should send an expected PUT request`, async(inject([MetadataProviderService, HttpTestingController],
(service: MetadataProviderService, backend: HttpTestingController) => {
const id = 'foo';
- const filter = new EntityAttributesFilter({id});
- service.update(filter).subscribe();
+ const provider = { resourceId: id };
+ service.update(provider).subscribe();
backend.expectOne((req: HttpRequest) => {
return req.url === `${service.base}${service.endpoint}/${id}`
@@ -61,8 +61,8 @@ describe(`Metadata Provider Service`, () => {
it(`should send an expected POST request`, async(inject([MetadataProviderService, HttpTestingController],
(service: MetadataProviderService, backend: HttpTestingController) => {
const id = 'foo';
- const filter = new EntityAttributesFilter({ id });
- service.save(filter).subscribe();
+ const provider = { resourceId: id };
+ service.save(provider).subscribe();
backend.expectOne((req: HttpRequest) => {
return req.url === `${service.base}${service.endpoint}`
diff --git a/ui/src/app/metadata/domain/service/provider.service.ts b/ui/src/app/metadata/domain/service/provider.service.ts
index 0644e4ab8..d82ee3d4b 100644
--- a/ui/src/app/metadata/domain/service/provider.service.ts
+++ b/ui/src/app/metadata/domain/service/provider.service.ts
@@ -2,31 +2,33 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
-import { MetadataFilter } from '../../domain/model';
+import { MetadataProvider } from '../../domain/model';
+import { FileBackedHttpMetadataProvider } from '../model/providers';
@Injectable()
export class MetadataProviderService {
- readonly endpoint = '/MetadataResolver/incommon/Filters';
+ readonly endpoint = '/MetadataResolvers';
readonly base = '/api';
constructor(
private http: HttpClient
) {}
- query(): Observable {
- return this.http.get(`${this.base}${this.endpoint}`, {});
+ query(): Observable {
+ return this.http.get(`${this.base}${this.endpoint}`, {});
}
- find(id: string): Observable {
+ find(id: string): Observable {
// console.log(id);
- return this.http.get(`${this.base}${this.endpoint}/${id}`);
+ return this.http.get(`${this.base}${this.endpoint}/${id}`);
}
- update(filter: MetadataFilter): Observable {
- return this.http.put(`${this.base}${this.endpoint}/${filter.id}`, filter);
+ update(provider: MetadataProvider): Observable {
+ return this.http.put(`${this.base}${this.endpoint}/${provider.resourceId}`, provider);
}
- save(filter: MetadataFilter): Observable {
- return this.http.post(`${this.base}${this.endpoint}`, filter);
+ save(provider: MetadataProvider): Observable {
+ const { metadataFilters, id, ...pruned } = provider as FileBackedHttpMetadataProvider;
+ return this.http.post(`${this.base}${this.endpoint}`, pruned);
}
}
diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts
index da2a08a84..5422f2d99 100644
--- a/ui/src/app/metadata/filter/effect/collection.effect.ts
+++ b/ui/src/app/metadata/filter/effect/collection.effect.ts
@@ -9,11 +9,10 @@ import { switchMap, map, catchError, tap } from 'rxjs/operators';
import * as actions from '../action/collection.action';
import { FilterCollectionActionTypes } from '../action/collection.action';
import * as fromFilter from '../reducer';
-
-import { MetadataProviderService } from '../../domain/service/provider.service';
import { MetadataFilter } from '../../domain/model';
import { removeNulls } from '../../../shared/util';
import { EntityAttributesFilter } from '../../domain/entity/filter/entity-attributes-filter';
+import { MetadataFilterService } from '../../domain/service/filter.service';
/* istanbul ignore next */
@Injectable()
@@ -23,7 +22,7 @@ export class FilterCollectionEffects {
loadFilters$ = this.actions$.pipe(
ofType(FilterCollectionActionTypes.LOAD_FILTER_REQUEST),
switchMap(() =>
- this.resolverService
+ this.filterService
.query()
.pipe(
map(filters => new actions.LoadFilterSuccess(filters)),
@@ -36,7 +35,7 @@ export class FilterCollectionEffects {
ofType(FilterCollectionActionTypes.SELECT_FILTER),
map(action => action.payload),
switchMap(id => {
- return this.resolverService
+ return this.filterService
.find(id)
.pipe(
map(p => new actions.SelectFilterSuccess(p)),
@@ -57,7 +56,7 @@ export class FilterCollectionEffects {
};
}),
switchMap(unsaved =>
- this.resolverService
+ this.filterService
.save(unsaved as MetadataFilter)
.pipe(
map(saved => new actions.AddFilterSuccess(saved)),
@@ -86,7 +85,7 @@ export class FilterCollectionEffects {
switchMap(filter => {
delete filter.modifiedDate;
delete filter.createdDate;
- return this.resolverService
+ return this.filterService
.update(filter)
.pipe(
map(p => new actions.UpdateFilterSuccess({
@@ -113,7 +112,7 @@ export class FilterCollectionEffects {
constructor(
private actions$: Actions,
private router: Router,
- private resolverService: MetadataProviderService,
+ private filterService: MetadataFilterService,
private store: Store
) { }
}
diff --git a/ui/src/app/metadata/metadata.component.spec.ts b/ui/src/app/metadata/metadata.component.spec.ts
index fb9d5f329..dffc99361 100644
--- a/ui/src/app/metadata/metadata.component.spec.ts
+++ b/ui/src/app/metadata/metadata.component.spec.ts
@@ -17,7 +17,7 @@ class TestHostComponent {
public componentUnderTest: MetadataPageComponent;
}
-describe('AppComponent', () => {
+describe('Metadata Root Component', () => {
let fixture: ComponentFixture;
let instance: TestHostComponent;
@@ -48,8 +48,8 @@ describe('AppComponent', () => {
fixture.detectChanges();
}));
- it('should create the app', async(() => {
+ it('should load metadata objects', async(() => {
expect(app).toBeTruthy();
- expect(store.dispatch).toHaveBeenCalledTimes(3);
+ expect(store.dispatch).toHaveBeenCalledTimes(4);
}));
});
diff --git a/ui/src/app/metadata/metadata.component.ts b/ui/src/app/metadata/metadata.component.ts
index 52ca814e2..62170d286 100644
--- a/ui/src/app/metadata/metadata.component.ts
+++ b/ui/src/app/metadata/metadata.component.ts
@@ -5,6 +5,7 @@ import { LoadResolverRequest } from './resolver/action/collection.action';
import { LoadFilterRequest } from './filter/action/collection.action';
import { LoadDraftRequest } from './resolver/action/draft.action';
import * as fromRoot from '../app.reducer';
+import { LoadProviderRequest } from './provider/action/collection.action';
@Component({
selector: 'metadata-page',
@@ -20,5 +21,6 @@ export class MetadataPageComponent {
this.store.dispatch(new LoadResolverRequest());
this.store.dispatch(new LoadFilterRequest());
this.store.dispatch(new LoadDraftRequest());
+ this.store.dispatch(new LoadProviderRequest());
}
}
diff --git a/ui/src/app/metadata/provider/action/collection.action.ts b/ui/src/app/metadata/provider/action/collection.action.ts
new file mode 100644
index 000000000..99e71c758
--- /dev/null
+++ b/ui/src/app/metadata/provider/action/collection.action.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+import { MetadataProvider } from '../../domain/model/metadata-provider';
+import { Update } from '@ngrx/entity';
+
+export enum ProviderCollectionActionTypes {
+ UPDATE_PROVIDER_REQUEST = '[Metadata Provider] Update Request',
+ UPDATE_PROVIDER_SUCCESS = '[Metadata Provider] Update Success',
+ UPDATE_PROVIDER_FAIL = '[Metadata Provider] Update Fail',
+
+ LOAD_PROVIDER_REQUEST = '[Metadata Provider Collection] Provider REQUEST',
+ LOAD_PROVIDER_SUCCESS = '[Metadata Provider Collection] Provider SUCCESS',
+ LOAD_PROVIDER_ERROR = '[Metadata Provider Collection] Provider ERROR',
+
+ ADD_PROVIDER_REQUEST = '[Metadata Provider Collection] Add Provider',
+ ADD_PROVIDER_SUCCESS = '[Metadata Provider Collection] Add Provider Success',
+ ADD_PROVIDER_FAIL = '[Metadata Provider Collection] Add Provider Fail',
+
+ REMOVE_PROVIDER_REQUEST = '[Metadata Provider Collection] Remove Provider Request',
+ REMOVE_PROVIDER_SUCCESS = '[Metadata Provider Collection] Remove Provider Success',
+ REMOVE_PROVIDER_FAIL = '[Metadata Provider Collection] Remove Provider Fail'
+}
+
+export class LoadProviderRequest implements Action {
+ readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_REQUEST;
+
+ constructor() { }
+}
+
+export class LoadProviderSuccess implements Action {
+ readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_SUCCESS;
+
+ constructor(public payload: MetadataProvider[]) { }
+}
+
+export class LoadProviderError implements Action {
+ readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_ERROR;
+
+ constructor(public payload: any) { }
+}
+
+export class UpdateProviderRequest implements Action {
+ readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_REQUEST;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class UpdateProviderSuccess implements Action {
+ readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS;
+
+ constructor(public payload: Update) { }
+}
+
+export class UpdateProviderFail implements Action {
+ readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_FAIL;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class AddProviderRequest implements Action {
+ readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_REQUEST;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class AddProviderSuccess implements Action {
+ readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class AddProviderFail implements Action {
+ readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_FAIL;
+
+ constructor(public payload: any) { }
+}
+
+export class RemoveProviderRequest implements Action {
+ readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_REQUEST;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class RemoveProviderSuccess implements Action {
+ readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_SUCCESS;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class RemoveProviderFail implements Action {
+ readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_FAIL;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export type ProviderCollectionActionsUnion =
+ | LoadProviderRequest
+ | LoadProviderSuccess
+ | LoadProviderError
+ | AddProviderRequest
+ | AddProviderSuccess
+ | AddProviderFail
+ | RemoveProviderRequest
+ | RemoveProviderSuccess
+ | RemoveProviderFail
+ | UpdateProviderRequest
+ | UpdateProviderSuccess
+ | UpdateProviderFail;
diff --git a/ui/src/app/metadata/provider/action/editor.action.ts b/ui/src/app/metadata/provider/action/editor.action.ts
index ca195716f..85ea00edd 100644
--- a/ui/src/app/metadata/provider/action/editor.action.ts
+++ b/ui/src/app/metadata/provider/action/editor.action.ts
@@ -6,7 +6,9 @@ export enum EditorActionTypes {
LOAD_SCHEMA_SUCCESS = '[Provider Editor] Load Schema Success',
LOAD_SCHEMA_FAIL = '[Provider Editor] Load Schema Fail',
- SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type'
+ SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type',
+
+ CLEAR = '[Provider Editor] Clear'
}
export class UpdateStatus implements Action {
@@ -39,9 +41,14 @@ export class SelectProviderType implements Action {
constructor(public payload: string) { }
}
+export class ClearEditor implements Action {
+ readonly type = EditorActionTypes.CLEAR;
+}
+
export type EditorActionUnion =
| UpdateStatus
| LoadSchemaRequest
| LoadSchemaSuccess
| LoadSchemaFail
- | SelectProviderType;
+ | SelectProviderType
+ | ClearEditor;
diff --git a/ui/src/app/metadata/provider/action/entity.action.ts b/ui/src/app/metadata/provider/action/entity.action.ts
index bff45b5f4..84f7dbd4a 100644
--- a/ui/src/app/metadata/provider/action/entity.action.ts
+++ b/ui/src/app/metadata/provider/action/entity.action.ts
@@ -3,13 +3,9 @@ import { MetadataProvider } from '../../domain/model';
export enum EntityActionTypes {
SELECT_PROVIDER = '[Provider Entity] Select Provider',
- CREATE_PROVIDER = '[Provider Entity] Create Provider',
UPDATE_PROVIDER = '[Provider Entity] Update Provider',
- SAVE_PROVIDER_REQUEST = '[Provider Entity] Save Provider Request',
- SAVE_PROVIDER_SUCCESS = '[Provider Entity] Save Provider Success',
- SAVE_PROVIDER_FAIL = '[Provider Entity] Save Provider Fail',
-
- RESET_CHANGES = '[Provider Entity] Reset Provider Changes'
+ CLEAR_PROVIDER = '[Provider Entity] Clear',
+ RESET_CHANGES = '[Provider Entity] Reset Changes'
}
export class SelectProvider implements Action {
@@ -18,34 +14,14 @@ export class SelectProvider implements Action {
constructor(public payload: MetadataProvider) { }
}
-export class CreateProvider implements Action {
- readonly type = EntityActionTypes.CREATE_PROVIDER;
-
- constructor(public payload: MetadataProvider) { }
-}
-
export class UpdateProvider implements Action {
readonly type = EntityActionTypes.UPDATE_PROVIDER;
constructor(public payload: Partial) { }
}
-export class SaveProviderRequest implements Action {
- readonly type = EntityActionTypes.SAVE_PROVIDER_REQUEST;
-
- constructor(public payload: MetadataProvider) { }
-}
-
-export class SaveProviderSuccess implements Action {
- readonly type = EntityActionTypes.SAVE_PROVIDER_SUCCESS;
-
- constructor(public payload: MetadataProvider) { }
-}
-
-export class SaveProviderFail implements Action {
- readonly type = EntityActionTypes.SAVE_PROVIDER_FAIL;
-
- constructor(public payload: Error) { }
+export class ClearProvider implements Action {
+ readonly type = EntityActionTypes.CLEAR_PROVIDER;
}
export class ResetChanges implements Action {
@@ -55,8 +31,5 @@ export class ResetChanges implements Action {
export type EntityActionUnion =
| SelectProvider
| UpdateProvider
- | SaveProviderRequest
- | SaveProviderSuccess
- | SaveProviderFail
- | CreateProvider
+ | ClearProvider
| ResetChanges;
diff --git a/ui/src/app/metadata/provider/component/provider-wizard-summary.component.html b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.html
new file mode 100644
index 000000000..a60ef4c1e
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/app/metadata/provider/component/provider-wizard-summary.component.spec.ts b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.spec.ts
new file mode 100644
index 000000000..d951464ad
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.spec.ts
@@ -0,0 +1,94 @@
+import { Component, ViewChild } from '@angular/core';
+import { TestBed, async, ComponentFixture } 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 { ProviderWizardSummaryComponent } from './provider-wizard-summary.component';
+import * as fromRoot from '../reducer';
+import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from 'ngx-schema-form';
+import * as fromWizard from '../../../wizard/reducer';
+import { Wizard } from '../../../wizard/model';
+import { MetadataProvider } from '../../domain/model';
+import { SummaryPropertyComponent } from './summary-property.component';
+import { SCHEMA } from '../../../../testing/form-schema.stub';
+import { MetadataProviderWizard } from '../model';
+
+@Component({
+ template: `
+
+ `
+})
+class TestHostComponent {
+ @ViewChild(ProviderWizardSummaryComponent)
+ public componentUnderTest: ProviderWizardSummaryComponent;
+
+ private _summary;
+
+ get summary(): { definition: Wizard, schema: { [id: string]: any }, model: any } {
+ return this._summary;
+ }
+
+ set summary(summary: { definition: Wizard, schema: { [id: string]: any }, model: any }) {
+ this._summary = summary;
+ }
+}
+
+describe('Provider Wizard Summary Component', () => {
+
+ let fixture: ComponentFixture;
+ let instance: TestHostComponent;
+ let app: ProviderWizardSummaryComponent;
+ let store: Store;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NgbDropdownModule.forRoot(),
+ RouterTestingModule,
+ SchemaFormModule.forRoot(),
+ StoreModule.forRoot({
+ provider: combineReducers(fromRoot.reducers),
+ wizard: combineReducers(fromWizard.reducers)
+ })
+ ],
+ declarations: [
+ ProviderWizardSummaryComponent,
+ SummaryPropertyComponent,
+ TestHostComponent
+ ],
+ providers: [
+ { provide: WidgetRegistry, useClass: DefaultWidgetRegistry }
+ ]
+ }).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ instance = fixture.componentInstance;
+ app = instance.componentUnderTest;
+ fixture.detectChanges();
+ }));
+
+ it('should instantiate the component', async(() => {
+ expect(app).toBeTruthy();
+ }));
+
+ describe('ngOnChanges', () => {
+ it('should set columns and sections if summary is provided', () => {
+ instance.summary = {
+ model: {
+ name: 'foo',
+ '@type': 'MetadataProvider'
+ },
+ schema: SCHEMA,
+ definition: MetadataProviderWizard
+ };
+ fixture.detectChanges();
+ expect(app.sections).toBeDefined();
+ expect(app.columns).toBeDefined();
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts
new file mode 100644
index 000000000..822e51337
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts
@@ -0,0 +1,83 @@
+import { Component, Input, SimpleChanges, OnChanges, Output, EventEmitter } from '@angular/core';
+import { Store } from '@ngrx/store';
+
+import * as fromProvider from '../reducer';
+import { Wizard, WizardStep } from '../../../wizard/model';
+import { MetadataProvider } from '../../domain/model';
+import { Property } from '../model/property';
+
+interface Section {
+ id: string;
+ index: number;
+ label: string;
+ properties: Property[];
+}
+
+function getStepProperties(schema: any, model: any): Property[] {
+ if (!schema || !schema.properties) { return []; }
+ return Object.keys(schema.properties).map(property => ({
+ name: schema.properties[property].title,
+ value: (model && model.hasOwnProperty(property)) ? model[property] : null,
+ type: schema.properties[property].type,
+ properties: schema.properties ? getStepProperties(
+ schema.properties[property],
+ (model && model.hasOwnProperty(property)) ? model[property] : null
+ ) : []
+ }));
+}
+
+@Component({
+ selector: 'provider-wizard-summary',
+ templateUrl: './provider-wizard-summary.component.html',
+ styleUrls: []
+})
+
+export class ProviderWizardSummaryComponent implements OnChanges {
+ @Input() summary: { definition: Wizard, schema: { [id: string]: any }, model: any };
+
+ @Output() onPageSelect: EventEmitter = new EventEmitter();
+
+ sections: Section[];
+ columns: Array[];
+
+ constructor(
+ private store: Store
+ ) {}
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.summary && this.summary) {
+ const schemas = this.summary.schema;
+ const model = this.summary.model;
+ const def = this.summary.definition;
+ const steps = def.steps;
+
+ this.sections = steps
+ .filter(step => step.id !== 'summary')
+ .map(
+ (step: WizardStep) => ({
+ id: step.id,
+ index: step.index,
+ label: step.label,
+ properties: getStepProperties(schemas[step.id], def.translate.formatter(model))
+ })
+ );
+
+ this.columns = this.sections.reduce((resultArray, item, index) => {
+ const chunkIndex = Math.floor(index / Math.round(this.sections.length / 2));
+
+ if (!resultArray[chunkIndex]) {
+ resultArray[chunkIndex] = [];
+ }
+
+ resultArray[chunkIndex].push(item);
+
+ return resultArray;
+ }, []);
+ }
+ }
+
+ gotoPage(page: string = ''): void {
+ this.onPageSelect.emit(page);
+ }
+}
+
diff --git a/ui/src/app/metadata/provider/component/summary-property.component.html b/ui/src/app/metadata/provider/component/summary-property.component.html
new file mode 100644
index 000000000..8f4781e47
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/summary-property.component.html
@@ -0,0 +1,18 @@
+
+
+
+
+ {{ property.name }}
+ {{ property.value || '-' }}
+
+
+
+
+ {{ property.name }}
+
+
+
+
+
+
+
diff --git a/ui/src/app/metadata/provider/component/summary-property.component.spec.ts b/ui/src/app/metadata/provider/component/summary-property.component.spec.ts
new file mode 100644
index 000000000..8a0fe498b
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/summary-property.component.spec.ts
@@ -0,0 +1,75 @@
+import { Component, ViewChild } from '@angular/core';
+import { TestBed, async, ComponentFixture } 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 { SummaryPropertyComponent } from './summary-property.component';
+import * as fromRoot from '../reducer';
+import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from 'ngx-schema-form';
+import * as fromWizard from '../../../wizard/reducer';
+import { Wizard } from '../../../wizard/model';
+import { MetadataProvider } from '../../domain/model';
+import { Property } from '../model/property';
+
+@Component({
+ template: `
+
+ `
+})
+class TestHostComponent {
+ @ViewChild(SummaryPropertyComponent)
+ public componentUnderTest: SummaryPropertyComponent;
+
+ private _property;
+
+ get property(): Property {
+ return this._property;
+ }
+
+ set property(prop: Property) {
+ this._property = prop;
+ }
+}
+
+describe('Summary Property Component', () => {
+
+ let fixture: ComponentFixture;
+ let instance: TestHostComponent;
+ let app: SummaryPropertyComponent;
+ let store: Store;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NgbDropdownModule.forRoot(),
+ RouterTestingModule,
+ SchemaFormModule.forRoot(),
+ StoreModule.forRoot({
+ provider: combineReducers(fromRoot.reducers),
+ wizard: combineReducers(fromWizard.reducers)
+ })
+ ],
+ declarations: [
+ SummaryPropertyComponent,
+ TestHostComponent
+ ],
+ providers: [
+ { provide: WidgetRegistry, useClass: DefaultWidgetRegistry }
+ ]
+ }).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ instance = fixture.componentInstance;
+ app = instance.componentUnderTest;
+ fixture.detectChanges();
+ }));
+
+ it('should instantiate the component', async(() => {
+ expect(app).toBeTruthy();
+ }));
+});
diff --git a/ui/src/app/metadata/provider/component/summary-property.component.ts b/ui/src/app/metadata/provider/component/summary-property.component.ts
new file mode 100644
index 000000000..6dbd0c716
--- /dev/null
+++ b/ui/src/app/metadata/provider/component/summary-property.component.ts
@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { Property } from '../model/property';
+
+@Component({
+ selector: 'summary-property',
+ templateUrl: './summary-property.component.html',
+ styleUrls: []
+})
+
+export class SummaryPropertyComponent {
+ @Input() property: Property;
+}
+
diff --git a/ui/src/app/metadata/provider/container/new-provider.component.scss b/ui/src/app/metadata/provider/container/new-provider.component.scss
deleted file mode 100644
index e3d0c671e..000000000
--- a/ui/src/app/metadata/provider/container/new-provider.component.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-// :host {
-// .provider-nav-option {
-// width: 160px;
-// }
-// }
diff --git a/ui/src/app/metadata/provider/container/new-provider.component.spec.ts b/ui/src/app/metadata/provider/container/new-provider.component.spec.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.html b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html
new file mode 100644
index 000000000..831e71fbb
--- /dev/null
+++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.spec.ts b/ui/src/app/metadata/provider/container/provider-wizard-step.component.spec.ts
new file mode 100644
index 000000000..eac91d512
--- /dev/null
+++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.spec.ts
@@ -0,0 +1,98 @@
+import { Component, ViewChild } from '@angular/core';
+import { TestBed, async, ComponentFixture } 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 { ProviderWizardStepComponent } from './provider-wizard-step.component';
+import * as fromRoot from '../reducer';
+import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from 'ngx-schema-form';
+import * as fromWizard from '../../../wizard/reducer';
+import { SCHEMA } from '../../../../testing/form-schema.stub';
+import { MetadataProviderWizard } from '../model';
+
+@Component({
+ template: `
+
+ `
+})
+class TestHostComponent {
+ @ViewChild(ProviderWizardStepComponent)
+ public componentUnderTest: ProviderWizardStepComponent;
+}
+
+describe('Provider Wizard Step Component', () => {
+
+ let fixture: ComponentFixture;
+ let instance: TestHostComponent;
+ let app: ProviderWizardStepComponent;
+ let store: Store;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NgbDropdownModule.forRoot(),
+ RouterTestingModule,
+ SchemaFormModule.forRoot(),
+ StoreModule.forRoot({
+ provider: combineReducers(fromRoot.reducers),
+ wizard: combineReducers(fromWizard.reducers)
+ })
+ ],
+ declarations: [
+ ProviderWizardStepComponent,
+ TestHostComponent
+ ],
+ providers: [
+ { provide: WidgetRegistry, useClass: DefaultWidgetRegistry }
+ ]
+ }).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ instance = fixture.componentInstance;
+ app = instance.componentUnderTest;
+ fixture.detectChanges();
+ }));
+
+ it('should instantiate the component', async(() => {
+ expect(app).toBeTruthy();
+ }));
+
+ describe('resetSelectedType method', () => {
+ it('should dispatch a SetDefinition action if the type has changed', () => {
+ app.resetSelectedType({ value: { name: 'foo', '@type': 'FileBackedHttpMetadataResolver' } }, SCHEMA, MetadataProviderWizard);
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+
+ it('should NOT dispatch a SetDefinition action if the type hasn\'t changed', () => {
+ app.resetSelectedType({ value: { name: 'foo', '@type': 'MetadataProvider' } }, SCHEMA, MetadataProviderWizard);
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('should NOT dispatch a SetDefinition action if the type isn\'t found', () => {
+ app.resetSelectedType({ value: { name: 'foo', '@type': 'FooProvider' } }, SCHEMA, MetadataProviderWizard);
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('should return changes and definition if no type supplied', () => {
+ app.resetSelectedType({ value: { name: 'foo' } }, SCHEMA, MetadataProviderWizard);
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('updateStatus method', () => {
+ it('should dispatch an UpdateStatus action', () => {
+ app.updateStatus({value: { name: 'notfound'} });
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+
+ it('should NOT dispatch a SetDefinition action if the type hasn\'t changed', () => {
+ app.updateStatus({ value: null });
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts
new file mode 100644
index 000000000..7d09b8ec1
--- /dev/null
+++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts
@@ -0,0 +1,134 @@
+import { Component, OnDestroy } from '@angular/core';
+import { Observable, Subject } from 'rxjs';
+import { withLatestFrom, map, distinctUntilChanged, skipWhile } from 'rxjs/operators';
+import { Store } from '@ngrx/store';
+
+import * as fromProvider from '../reducer';
+import * as fromWizard from '../../../wizard/reducer';
+
+import { SetDefinition } from '../../../wizard/action/wizard.action';
+import { UpdateStatus } from '../action/editor.action';
+import { Wizard } from '../../../wizard/model';
+import { MetadataProvider } from '../../domain/model';
+import { MetadataProviderTypes, MetadataProviderWizard } from '../model';
+import { UpdateProvider } from '../action/entity.action';
+import { pick } from '../../../shared/util';
+
+@Component({
+ selector: 'provider-wizard-step',
+ templateUrl: './provider-wizard-step.component.html',
+ styleUrls: []
+})
+
+export class ProviderWizardStepComponent implements OnDestroy {
+ valueChangeSubject = new Subject>();
+ private valueChangeEmitted$ = this.valueChangeSubject.asObservable();
+
+ statusChangeSubject = new Subject>();
+ private statusChangeEmitted$ = this.statusChangeSubject.asObservable();
+
+ schema$: Observable;
+ schema: any;
+ definition$: Observable>;
+ changes$: Observable;
+ currentPage: string;
+ valid$: Observable;
+ model$: Observable;
+
+ namesList: string[] = [];
+
+ validators = {
+ '/': (value, property, form_current) => {
+ let errors;
+ // iterate all customer
+ Object.keys(value).forEach((key) => {
+ const item = value[key];
+ const validatorKey = `/${key}`;
+ const validator = this.validators.hasOwnProperty(validatorKey) ? this.validators[validatorKey] : null;
+ const error = validator ? validator(item, { path: `/${key}` }, form_current) : null;
+ if (error) {
+ errors = errors || [];
+ errors.push(error);
+ }
+ });
+ return errors;
+ },
+ '/name': (value, property, form) => {
+ const err = this.namesList.indexOf(value) > -1 ? {
+ code: 'INVALID_NAME',
+ path: `#${property.path}`,
+ message: 'Name must be unique.',
+ params: [value]
+ } : null;
+ return err;
+ }
+ };
+
+ constructor(
+ private store: Store,
+ ) {
+ this.schema$ = this.store.select(fromProvider.getSchema);
+ this.definition$ = this.store.select(fromWizard.getWizardDefinition);
+ this.changes$ = this.store.select(fromProvider.getEntityChanges);
+
+ this.store.select(fromProvider.getProviderNames).subscribe(list => this.namesList = list);
+
+ this.model$ = this.schema$.pipe(
+ withLatestFrom(
+ this.store.select(fromWizard.getModel),
+ this.changes$,
+ this.definition$
+ ),
+ map(([schema, model, changes, definition]) => ({
+ model: {
+ ...model,
+ ...changes
+ },
+ definition
+ })),
+ skipWhile(({ model, definition }) => !definition || !model),
+ map(({ model, definition }) => definition.translate.formatter(model))
+ );
+
+ this.valueChangeEmitted$.pipe(
+ withLatestFrom(this.schema$, this.definition$),
+ map(([changes, schema, definition]) => this.resetSelectedType(changes, schema, definition)),
+ skipWhile(({ changes, definition }) => !definition || !changes),
+ map(({ changes, definition }) => definition.translate.parser(changes))
+ )
+ .subscribe(changes => this.store.dispatch(new UpdateProvider(changes)));
+
+ this.statusChangeEmitted$.pipe(distinctUntilChanged()).subscribe(errors => this.updateStatus(errors));
+
+ this.store.select(fromWizard.getWizardIndex).subscribe(i => this.currentPage = i);
+ }
+
+ resetSelectedType(changes: any, schema: any, definition: any): { changes: any, definition: any } {
+ const type = changes.value['@type'];
+ if (type && type !== definition.type) {
+ const newDefinition = MetadataProviderTypes.find(def => def.type === type);
+ if (newDefinition) {
+ this.store.dispatch(new SetDefinition({
+ ...MetadataProviderWizard,
+ ...newDefinition,
+ steps: [
+ ...MetadataProviderWizard.steps,
+ ...newDefinition.steps
+ ]
+ }));
+ changes = { value: pick(Object.keys(schema.properties))(changes.value) };
+ }
+ }
+ return { changes: changes.value, definition };
+ }
+
+ updateStatus(errors: any): void {
+ const status = { [this.currentPage]: !(errors.value) ? 'VALID' : 'INVALID' };
+ this.store.dispatch(new UpdateStatus(status));
+ }
+
+ ngOnDestroy() {
+ this.valueChangeSubject.complete();
+ }
+}
+
diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.html b/ui/src/app/metadata/provider/container/provider-wizard.component.html
index 58adb7221..196081a1f 100644
--- a/ui/src/app/metadata/provider/container/provider-wizard.component.html
+++ b/ui/src/app/metadata/provider/container/provider-wizard.component.html
@@ -1,18 +1,17 @@
diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.scss b/ui/src/app/metadata/provider/container/provider-wizard.component.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts
new file mode 100644
index 000000000..043663260
--- /dev/null
+++ b/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts
@@ -0,0 +1,63 @@
+import { Component, ViewChild } from '@angular/core';
+import { TestBed, async, ComponentFixture } 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 { ProviderWizardComponent } from './provider-wizard.component';
+import * as fromRoot from '../reducer';
+import { WizardModule } from '../../../wizard/wizard.module';
+import { ProviderWizardSummaryComponent } from '../component/provider-wizard-summary.component';
+import { SummaryPropertyComponent } from '../component/summary-property.component';
+import * as fromWizard from '../../../wizard/reducer';
+
+@Component({
+ template: `
+
+ `
+})
+class TestHostComponent {
+ @ViewChild(ProviderWizardComponent)
+ public componentUnderTest: ProviderWizardComponent;
+}
+
+describe('Provider Wizard Component', () => {
+
+ let fixture: ComponentFixture;
+ let instance: TestHostComponent;
+ let app: ProviderWizardComponent;
+ let store: Store;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ WizardModule,
+ NgbDropdownModule.forRoot(),
+ RouterTestingModule,
+ StoreModule.forRoot({
+ provider: combineReducers(fromRoot.reducers),
+ wizard: combineReducers(fromWizard.reducers)
+ })
+ ],
+ declarations: [
+ ProviderWizardComponent,
+ SummaryPropertyComponent,
+ ProviderWizardSummaryComponent,
+ TestHostComponent
+ ]
+ }).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ instance = fixture.componentInstance;
+ app = instance.componentUnderTest;
+ fixture.detectChanges();
+ }));
+
+ it('should instantiate the component', async(() => {
+ expect(app).toBeTruthy();
+ }));
+});
diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts
index 36861d3cf..ee18b72b0 100644
--- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts
+++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts
@@ -1,61 +1,56 @@
-
import { Component, OnDestroy } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
-import { Subscription, Observable, Subject } from 'rxjs';
-import { distinctUntilChanged, map, withLatestFrom } from 'rxjs/operators';
+import { Observable, combineLatest } from 'rxjs';
import { Store } from '@ngrx/store';
import * as fromProvider from '../reducer';
import * as fromWizard from '../../../wizard/reducer';
-
-import { SetIndex, SetDisabled, UpdateDefinition, WizardActionTypes, Next, SetDefinition } from '../../../wizard/action/wizard.action';
-import { LoadSchemaRequest, UpdateStatus } from '../action/editor.action';
+import { SetIndex, SetDisabled, ClearWizard } from '../../../wizard/action/wizard.action';
+import { LoadSchemaRequest, ClearEditor } from '../action/editor.action';
import { startWith } from 'rxjs/operators';
import { Wizard, WizardStep } from '../../../wizard/model';
import { MetadataProvider } from '../../domain/model';
-import { MetadataProviderTypes, MetadataProviderWizard } from '../model';
-import { UpdateProvider } from '../action/entity.action';
-import { pick } from '../../../shared/util';
+import { ClearProvider } from '../action/entity.action';
+import { Router, ActivatedRoute } from '@angular/router';
+import { map } from 'rxjs/operators';
+import { AddProviderRequest } from '../action/collection.action';
+
@Component({
- selector: 'provider-wizard-page',
+ selector: 'provider-wizard',
templateUrl: './provider-wizard.component.html',
- styleUrls: ['./provider-wizard.component.scss']
+ styleUrls: []
})
export class ProviderWizardComponent implements OnDestroy {
- actionsSubscription: Subscription;
-
- changeSubject = new Subject>();
- private changeEmitted$ = this.changeSubject.asObservable();
-
- schema$: Observable;
- schema: any;
definition$: Observable>;
changes$: Observable;
+ model$: Observable;
currentPage: string;
valid$: Observable;
- formModel: any;
-
nextStep: WizardStep;
previousStep: WizardStep;
+ summary$: Observable<{ definition: Wizard, schema: { [id: string]: any }, model: any }>;
+
+ provider: MetadataProvider;
+
constructor(
- private store: Store
+ private store: Store,
+ private router: Router,
+ private route: ActivatedRoute
) {
this.store
.select(fromWizard.getCurrentWizardSchema)
.subscribe(s => {
this.store.dispatch(new LoadSchemaRequest(s));
});
-
- this.schema$ = this.store.select(fromProvider.getSchema);
this.valid$ = this.store.select(fromProvider.getEditorIsValid);
- this.definition$ = this.store.select(fromWizard.getWizardDefinition);
this.changes$ = this.store.select(fromProvider.getEntityChanges);
+
this.store.select(fromWizard.getNext).subscribe(n => this.nextStep = n);
this.store.select(fromWizard.getPrevious).subscribe(p => this.previousStep = p);
+ this.store.select(fromWizard.getWizardIndex).subscribe(i => this.currentPage = i);
this.valid$
.pipe(startWith(false))
@@ -63,43 +58,25 @@ export class ProviderWizardComponent implements OnDestroy {
this.store.dispatch(new SetDisabled(!valid));
});
- this.schema$.subscribe(s => this.schema = s);
-
- this.changeEmitted$
- .pipe(
- withLatestFrom(this.schema$, this.definition$),
- )
- .subscribe(
- ([changes, schema, definition]) => {
- const type = changes.value['@type'];
- if (type && type !== definition.type) {
- const newDefinition = MetadataProviderTypes.find(def => def.type === type);
- if (newDefinition) {
- this.store.dispatch(new SetDefinition({
- ...MetadataProviderWizard,
- ...newDefinition,
- steps: [
- ...MetadataProviderWizard.steps,
- ...newDefinition.steps
- ]
- }));
- changes = { value: pick(Object.keys(schema.properties))(changes.value) };
- }
- }
- this.store.dispatch(new UpdateProvider(changes.value));
- }
- );
+ this.summary$ = combineLatest(
+ this.store.select(fromWizard.getWizardDefinition),
+ this.store.select(fromWizard.getSchemaCollection),
+ this.store.select(fromProvider.getEntityChanges)
+ ).pipe(
+ map(([ definition, schema, model ]) => ({ definition, schema, model }))
+ );
+
+ this.changes$.subscribe(c => this.provider = c);
}
ngOnDestroy() {
- this.actionsSubscription.unsubscribe();
- this.changeSubject.complete();
+ this.store.dispatch(new ClearProvider());
+ this.store.dispatch(new ClearWizard());
+ this.store.dispatch(new ClearEditor());
}
next(): void {
- if (this.nextStep) {
- this.store.dispatch(new SetIndex(this.nextStep.id));
- }
+ this.store.dispatch(new SetIndex(this.nextStep.id));
}
previous(): void {
@@ -107,12 +84,11 @@ export class ProviderWizardComponent implements OnDestroy {
}
save(): void {
- console.log('Save!');
+ this.store.dispatch(new AddProviderRequest(this.provider));
}
- onStatusChange(value): void {
- const status = { [this.currentPage]: value ? 'VALID' : 'INVALID' };
- this.store.dispatch(new UpdateStatus(status));
+ gotoPage(page: string): void {
+ this.store.dispatch(new SetIndex(page));
}
}
diff --git a/ui/src/app/metadata/provider/container/new-provider.component.html b/ui/src/app/metadata/provider/container/provider.component.html
similarity index 100%
rename from ui/src/app/metadata/provider/container/new-provider.component.html
rename to ui/src/app/metadata/provider/container/provider.component.html
diff --git a/ui/src/app/metadata/provider/container/provider.component.spec.ts b/ui/src/app/metadata/provider/container/provider.component.spec.ts
new file mode 100644
index 000000000..3f1363cdf
--- /dev/null
+++ b/ui/src/app/metadata/provider/container/provider.component.spec.ts
@@ -0,0 +1,57 @@
+import { Component, ViewChild } from '@angular/core';
+import { TestBed, async, ComponentFixture } 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 { ProviderComponent } from './provider.component';
+import * as fromRoot from '../reducer';
+import * as fromWizard from '../../../wizard/reducer';
+
+@Component({
+ template: `
+
+ `
+})
+class TestHostComponent {
+ @ViewChild(ProviderComponent)
+ public componentUnderTest: ProviderComponent;
+}
+
+describe('Provider Component', () => {
+
+ let fixture: ComponentFixture;
+ let instance: TestHostComponent;
+ let app: ProviderComponent;
+ let store: Store;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NgbDropdownModule.forRoot(),
+ RouterTestingModule,
+ StoreModule.forRoot({
+ provider: combineReducers(fromRoot.reducers),
+ wizard: combineReducers(fromWizard.reducers)
+ })
+ ],
+ declarations: [
+ ProviderComponent,
+ TestHostComponent
+ ],
+ }).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
+
+ fixture = TestBed.createComponent(TestHostComponent);
+ instance = fixture.componentInstance;
+ app = instance.componentUnderTest;
+ fixture.detectChanges();
+ }));
+
+ it('should instantiate the component', async(() => {
+ expect(app).toBeTruthy();
+ }));
+});
diff --git a/ui/src/app/metadata/provider/container/new-provider.component.ts b/ui/src/app/metadata/provider/container/provider.component.ts
similarity index 55%
rename from ui/src/app/metadata/provider/container/new-provider.component.ts
rename to ui/src/app/metadata/provider/container/provider.component.ts
index d7581c566..afaf48a27 100644
--- a/ui/src/app/metadata/provider/container/new-provider.component.ts
+++ b/ui/src/app/metadata/provider/container/provider.component.ts
@@ -1,25 +1,17 @@
import { Component } from '@angular/core';
-import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { Store } from '@ngrx/store';
-import { ActivatedRoute } from '@angular/router';
-
-
-import { MetadataProviderTypes } from '../model';
import * as fromProvider from '../reducer';
import { SetDefinition, SetIndex } from '../../../wizard/action/wizard.action';
import { MetadataProviderWizard } from '../model';
@Component({
- selector: 'new-provider-page',
- templateUrl: './new-provider.component.html',
- styleUrls: ['./new-provider.component.scss']
+ selector: 'provider-page',
+ templateUrl: './provider.component.html',
+ styleUrls: []
})
-export class NewProviderComponent {
- types = MetadataProviderTypes;
-
+export class ProviderComponent {
constructor(
- private fb: FormBuilder,
private store: Store
) {
this.store.dispatch(new SetDefinition(MetadataProviderWizard));
diff --git a/ui/src/app/metadata/provider/effect/collection.effect.ts b/ui/src/app/metadata/provider/effect/collection.effect.ts
new file mode 100644
index 000000000..5a471dfdd
--- /dev/null
+++ b/ui/src/app/metadata/provider/effect/collection.effect.ts
@@ -0,0 +1,69 @@
+import { Injectable } from '@angular/core';
+import { Effect, Actions, ofType } from '@ngrx/effects';
+import { Router } from '@angular/router';
+
+import { of } from 'rxjs';
+import { map, catchError, switchMap, tap } from 'rxjs/operators';
+import {
+ ProviderCollectionActionsUnion,
+ ProviderCollectionActionTypes,
+ AddProviderRequest,
+ AddProviderSuccess,
+ AddProviderFail,
+ LoadProviderRequest,
+ LoadProviderSuccess,
+ LoadProviderError
+} from '../action/collection.action';
+import { MetadataProviderService } from '../../domain/service/provider.service';
+
+/* istanbul ignore next */
+@Injectable()
+export class CollectionEffects {
+
+ @Effect()
+ loadFilters$ = this.actions$.pipe(
+ ofType(ProviderCollectionActionTypes.LOAD_PROVIDER_REQUEST),
+ switchMap(() =>
+ this.providerService
+ .query()
+ .pipe(
+ map(providers => new LoadProviderSuccess(providers)),
+ catchError(error => of(new LoadProviderError(error)))
+ )
+ )
+ );
+
+ @Effect()
+ createProvider$ = this.actions$.pipe(
+ ofType(ProviderCollectionActionTypes.ADD_PROVIDER_REQUEST),
+ map(action => action.payload),
+ switchMap(provider =>
+ this.providerService
+ .save(provider)
+ .pipe(
+ map(p => new AddProviderSuccess(p)),
+ catchError((e) => of(new AddProviderFail(e)))
+ )
+ )
+ );
+
+ @Effect({ dispatch: false })
+ createProviderSuccessRedirect$ = this.actions$.pipe(
+ ofType(ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS),
+ map(action => action.payload),
+ tap(provider => this.router.navigate(['metadata']))
+ );
+
+ @Effect()
+ addResolverSuccessReload$ = this.actions$.pipe(
+ ofType(ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS),
+ map(action => action.payload),
+ map(provider => new LoadProviderRequest())
+ );
+
+ constructor(
+ private actions$: Actions,
+ private router: Router,
+ private providerService: MetadataProviderService
+ ) { }
+} /* istanbul ignore next */
diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts
index 7fc04f3bc..bbf1bcc48 100644
--- a/ui/src/app/metadata/provider/effect/editor.effect.ts
+++ b/ui/src/app/metadata/provider/effect/editor.effect.ts
@@ -8,11 +8,14 @@ import {
LoadSchemaFail,
EditorActionTypes
} from '../action/editor.action';
-import { map, switchMap, catchError } from 'rxjs/operators';
+import { map, switchMap, catchError, withLatestFrom } from 'rxjs/operators';
import { of } from 'rxjs';
-import { SetDefinition, WizardActionTypes } from '../../../wizard/action/wizard.action';
+import { SetDefinition, WizardActionTypes, AddSchema } from '../../../wizard/action/wizard.action';
import { ResetChanges } from '../action/entity.action';
+import * as fromWizard from '../../../wizard/reducer';
+import { Store } from '@ngrx/store';
+
@Injectable()
export class EditorEffects {
@@ -30,6 +33,14 @@ export class EditorEffects {
)
);
+ @Effect()
+ $loadSchemaSuccess = this.actions$.pipe(
+ ofType(EditorActionTypes.LOAD_SCHEMA_SUCCESS),
+ map(action => action.payload),
+ withLatestFrom(this.store.select(fromWizard.getWizardIndex)),
+ map(([schema, id]) => new AddSchema({ id, schema }))
+ );
+
@Effect()
$resetChanges = this.actions$.pipe(
ofType(WizardActionTypes.SET_DEFINITION),
@@ -38,6 +49,7 @@ export class EditorEffects {
constructor(
private schemaService: SchemaService,
+ private store: Store,
private actions$: Actions
) { }
} /* istanbul ignore next */
diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts
new file mode 100644
index 000000000..4bba62c7d
--- /dev/null
+++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts
@@ -0,0 +1,110 @@
+import { FileBackedHttpMetadataProviderWizard } from './file-backed-http.provider.form';
+import { FileBackedHttpMetadataProvider } from '../../domain/model/providers';
+
+describe('FileBackedHttpMetadataProviderWizard', () => {
+
+ const parser = FileBackedHttpMetadataProviderWizard.translate.parser;
+ const formatter = FileBackedHttpMetadataProviderWizard.translate.formatter;
+
+ const requiredValidUntilFilter = {
+ maxValidityInterval: 1,
+ '@type': 'RequiredValidUntil'
+ };
+
+ const signatureValidationFilter = {
+ requireSignedRoot: true,
+ certificateFile: 'foo',
+ '@type': 'SignatureValidation'
+ };
+
+ const entityRoleWhiteListFilter = {
+ retainedRoles: ['foo', 'bar'],
+ removeRolelessEntityDescriptors: true,
+ removeEmptyEntitiesDescriptors: true,
+ '@type': 'EntityRoleWhiteList'
+ };
+
+ describe('parser', () => {
+ it('should transform the filters object to an array', () => {
+ let model = {
+ name: 'foo',
+ '@type': 'FileBackedHttpMetadataProvider',
+ enabled: true,
+ resourceId: 'foo',
+ metadataFilters: {
+ RequiredValidUntil: requiredValidUntilFilter,
+ SignatureValidation: signatureValidationFilter,
+ EntityRoleWhiteList: entityRoleWhiteListFilter
+ }
+ };
+ expect(
+ parser(model)
+ ).toEqual(
+ {
+ ...model,
+ metadataFilters: [
+ requiredValidUntilFilter,
+ signatureValidationFilter,
+ entityRoleWhiteListFilter
+ ]
+ }
+ );
+ });
+
+ it('should return the object if metadataFilters is not provided', () => {
+ let model = {
+ name: 'foo',
+ '@type': 'FileBackedHttpMetadataProvider',
+ enabled: true,
+ resourceId: 'foo'
+ };
+ expect(
+ parser(model)
+ ).toEqual(
+ model
+ );
+ });
+ });
+
+ describe('formatter', () => {
+ it('should transform the filters object to an array', () => {
+ let model = {
+ name: 'foo',
+ '@type': 'FileBackedHttpMetadataProvider',
+ enabled: true,
+ resourceId: 'foo',
+ metadataFilters: [
+ requiredValidUntilFilter,
+ signatureValidationFilter,
+ entityRoleWhiteListFilter
+ ]
+ };
+ expect(
+ formatter(model)
+ ).toEqual(
+ {
+ ...model,
+ metadataFilters: {
+ RequiredValidUntil: requiredValidUntilFilter,
+ SignatureValidation: signatureValidationFilter,
+ EntityRoleWhiteList: entityRoleWhiteListFilter
+ }
+ }
+ );
+ });
+
+ it('should return the object if metadataFilters is not provided', () => {
+ let model = {
+ name: 'foo',
+ '@type': 'FileBackedHttpMetadataProvider',
+ enabled: true,
+ resourceId: 'foo'
+ };
+ expect(
+ formatter(model)
+ ).toEqual(
+ model
+ );
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts
index 4f77f9e35..41723be8f 100644
--- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts
+++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts
@@ -1,17 +1,39 @@
import { Wizard } from '../../../wizard/model';
-import { MetadataProvider } from '../../domain/model';
+import { FileBackedHttpMetadataProvider } from '../../domain/model/providers/file-backed-http-metadata-provider';
-export const FileBackedHttpMetadataProviderWizard: Wizard = {
+export const FileBackedHttpMetadataProviderWizard: Wizard = {
label: 'FileBackedHttpMetadataProvider',
- type: '@FileBackedHttpMetadataProvider',
+ type: 'FileBackedHttpMetadataResolver',
+ translate: {
+ parser: (changes: any): FileBackedHttpMetadataProvider => changes.metadataFilters ? ({
+ ...changes,
+ metadataFilters: [
+ ...Object.keys(changes.metadataFilters).reduce((collection, filterName) => ([
+ ...collection,
+ {
+ ...changes.metadataFilters[filterName],
+ '@type': filterName
+ }
+ ]), [])
+ ]
+ }) : changes,
+ formatter: (changes: FileBackedHttpMetadataProvider): any => changes.metadataFilters ? ({
+ ...changes,
+ metadataFilters: {
+ ...(changes.metadataFilters || []).reduce((collection, filter) => ({
+ ...collection,
+ [filter['@type']]: filter
+ }), {})
+ }
+ }) : changes
+ },
steps: [
{
id: 'common',
label: 'Common Attributes',
index: 2,
initialValues: [],
- schema: 'assets/schema/provider/filebacked-http-common.schema.json',
- parser: (changes: Partial, schema: any) => ({ name: '', '@type': '' })
+ schema: 'assets/schema/provider/filebacked-http-common.schema.json'
},
{
id: 'reloading',
@@ -24,8 +46,17 @@ export const FileBackedHttpMetadataProviderWizard: Wizard = {
id: 'filters',
label: 'Metadata Filter Plugins',
index: 4,
- initialValues: [],
+ initialValues: [
+ { key: 'metadataFilters', value: [] }
+ ],
schema: 'assets/schema/provider/filebacked-http-filters.schema.json'
+ },
+ {
+ id: 'summary',
+ label: 'FINISH SUMMARY AND VALIDATION',
+ index: null,
+ initialValues: [],
+ schema: 'assets/schema/provider/metadata-provider-summary.schema.json'
}
]
};
diff --git a/ui/src/app/metadata/provider/model/property.ts b/ui/src/app/metadata/provider/model/property.ts
new file mode 100644
index 000000000..031dce75c
--- /dev/null
+++ b/ui/src/app/metadata/provider/model/property.ts
@@ -0,0 +1,6 @@
+export interface Property {
+ type: string;
+ name: string;
+ value: string[];
+ properties: Property[];
+}
diff --git a/ui/src/app/metadata/provider/model/provider.form.ts b/ui/src/app/metadata/provider/model/provider.form.ts
index 133de467f..698d20134 100644
--- a/ui/src/app/metadata/provider/model/provider.form.ts
+++ b/ui/src/app/metadata/provider/model/provider.form.ts
@@ -4,7 +4,11 @@ import { Metadata } from '../../domain/domain.type';
export const MetadataProviderWizard: Wizard = {
label: 'MetadataProvider',
- type: '@MetadataProvider',
+ type: 'MetadataProvider',
+ translate: {
+ parser: changes => changes,
+ formatter: model => model
+ },
steps: [
{
id: 'new',
diff --git a/ui/src/app/metadata/provider/provider.module.ts b/ui/src/app/metadata/provider/provider.module.ts
index 5379d9c94..104bae13d 100644
--- a/ui/src/app/metadata/provider/provider.module.ts
+++ b/ui/src/app/metadata/provider/provider.module.ts
@@ -5,22 +5,29 @@ import { RouterModule } from '@angular/router';
import { StoreModule } from '@ngrx/store';
import { ProviderWizardComponent } from './container/provider-wizard.component';
-import { NewProviderComponent } from './container/new-provider.component';
+import { ProviderWizardStepComponent } from './container/provider-wizard-step.component';
+import { ProviderWizardSummaryComponent } from './component/provider-wizard-summary.component';
+import { ProviderComponent } from './container/provider.component';
import { WizardModule } from '../../wizard/wizard.module';
import * as fromProvider from './reducer';
import { EffectsModule } from '@ngrx/effects';
import { EditorEffects } from './effect/editor.effect';
-// import { SchemaFormModule } from '../../schema-form/form.module';
-import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from 'ngx-schema-form';
+import { WidgetRegistry} from 'ngx-schema-form';
import { FormModule } from '../../schema-form/schema-form.module';
import { CustomWidgetRegistry } from '../../schema-form/registry';
+import { SummaryPropertyComponent } from './component/summary-property.component';
+import { CollectionEffects } from './effect/collection.effect';
+import { SharedModule } from '../../shared/shared.module';
@NgModule({
declarations: [
- NewProviderComponent,
- ProviderWizardComponent
+ ProviderComponent,
+ ProviderWizardComponent,
+ ProviderWizardStepComponent,
+ ProviderWizardSummaryComponent,
+ SummaryPropertyComponent
],
entryComponents: [],
imports: [
@@ -28,6 +35,7 @@ import { CustomWidgetRegistry } from '../../schema-form/registry';
CommonModule,
WizardModule,
RouterModule,
+ SharedModule,
FormModule
],
exports: []
@@ -47,7 +55,7 @@ export class ProviderModule {
imports: [
ProviderModule,
StoreModule.forFeature('provider', fromProvider.reducers),
- EffectsModule.forFeature([EditorEffects])
+ EffectsModule.forFeature([EditorEffects, CollectionEffects])
]
})
export class RootProviderModule { }
diff --git a/ui/src/app/metadata/provider/provider.routing.ts b/ui/src/app/metadata/provider/provider.routing.ts
index ad783dc6a..98cdddd85 100644
--- a/ui/src/app/metadata/provider/provider.routing.ts
+++ b/ui/src/app/metadata/provider/provider.routing.ts
@@ -1,12 +1,13 @@
import { Routes } from '@angular/router';
-import { NewProviderComponent } from './container/new-provider.component';
+import { ProviderComponent } from './container/provider.component';
import { ProviderWizardComponent } from './container/provider-wizard.component';
+import { ProviderWizardStepComponent } from './container/provider-wizard-step.component';
export const ProviderRoutes: Routes = [
{
path: 'provider',
- component: NewProviderComponent,
+ component: ProviderComponent,
children: [
{
path: 'wizard',
@@ -14,9 +15,15 @@ export const ProviderRoutes: Routes = [
pathMatch: 'prefix'
},
{
- path: 'wizard/new',
+ path: 'wizard',
component: ProviderWizardComponent,
- canActivate: []
+ canActivate: [],
+ children: [
+ {
+ path: 'new',
+ component: ProviderWizardStepComponent
+ }
+ ]
}
]
}
diff --git a/ui/src/app/metadata/provider/reducer/collection.reducer.spec.ts b/ui/src/app/metadata/provider/reducer/collection.reducer.spec.ts
new file mode 100644
index 000000000..4204ebe65
--- /dev/null
+++ b/ui/src/app/metadata/provider/reducer/collection.reducer.spec.ts
@@ -0,0 +1,50 @@
+import { reducer } from './collection.reducer';
+import * as fromProvider from './collection.reducer';
+import {
+ ProviderCollectionActionTypes,
+ LoadProviderSuccess,
+ UpdateProviderSuccess
+} from '../action/collection.action';
+
+const snapshot: fromProvider.CollectionState = {
+ ids: [],
+ entities: {},
+ selectedProviderId: null,
+ loaded: false
+};
+
+describe('Provider Collection Reducer', () => {
+ describe('undefined action', () => {
+ it('should return the default state', () => {
+ const result = reducer(snapshot, {} as any);
+
+ expect(result).toEqual(snapshot);
+ });
+ });
+
+ describe(`${ProviderCollectionActionTypes.LOAD_PROVIDER_SUCCESS}`, () => {
+ it('should add the loaded providers to the collection', () => {
+ spyOn(fromProvider.adapter, 'addAll').and.callThrough();
+ const providers = [
+ { resourceId: 'foo', name: 'foo', '@type': 'foo', enabled: true, createdDate: new Date().toLocaleDateString() },
+ { resourceId: 'bar', name: 'bar', '@type': 'bar', enabled: false, createdDate: new Date().toLocaleDateString() }
+ ];
+ const action = new LoadProviderSuccess(providers);
+ const result = reducer(snapshot, action);
+ expect(fromProvider.adapter.addAll).toHaveBeenCalled();
+ });
+ });
+
+ describe(`${ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS}`, () => {
+ it('should add the loaded providers to the collection', () => {
+ spyOn(fromProvider.adapter, 'updateOne').and.callThrough();
+ const update = {
+ id: 'foo',
+ changes: { resourceId: 'foo', name: 'bar', createdDate: new Date().toLocaleDateString() },
+ };
+ const action = new UpdateProviderSuccess(update);
+ const result = reducer(snapshot, action);
+ expect(fromProvider.adapter.updateOne).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/reducer/collection.reducer.ts b/ui/src/app/metadata/provider/reducer/collection.reducer.ts
new file mode 100644
index 000000000..9a625fa79
--- /dev/null
+++ b/ui/src/app/metadata/provider/reducer/collection.reducer.ts
@@ -0,0 +1,52 @@
+import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
+import { ProviderCollectionActionTypes, ProviderCollectionActionsUnion } from '../action/collection.action';
+import { MetadataProvider } from '../../domain/model';
+
+export interface CollectionState extends EntityState {
+ selectedProviderId: string | null;
+ loaded: boolean;
+}
+
+export function sortByDate(a: MetadataProvider, b: MetadataProvider): number {
+ return a.createdDate.localeCompare(b.createdDate);
+}
+
+export const adapter: EntityAdapter = createEntityAdapter({
+ sortComparer: sortByDate,
+ selectId: (model: MetadataProvider) => model.resourceId
+});
+
+export const initialState: CollectionState = adapter.getInitialState({
+ selectedProviderId: null,
+ loaded: false
+});
+
+export function reducer(state = initialState, action: ProviderCollectionActionsUnion): CollectionState {
+ switch (action.type) {
+ case ProviderCollectionActionTypes.LOAD_PROVIDER_SUCCESS: {
+ let s = adapter.addAll(action.payload, {
+ ...state,
+ selectedProviderId: state.selectedProviderId,
+ loaded: true
+ });
+ return s;
+ }
+
+ case ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS: {
+ return adapter.updateOne(action.payload, state);
+ }
+
+ default: {
+ return state;
+ }
+ }
+}
+
+export const getSelectedProviderId = (state: CollectionState) => state.selectedProviderId;
+export const getIsLoaded = (state: CollectionState) => state.loaded;
+export const {
+ selectIds: selectProviderIds,
+ selectEntities: selectProviderEntities,
+ selectAll: selectAllProviders,
+ selectTotal: selectProviderTotal
+} = adapter.getSelectors();
diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.spec.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.spec.ts
new file mode 100644
index 000000000..d1b58da34
--- /dev/null
+++ b/ui/src/app/metadata/provider/reducer/editor.reducer.spec.ts
@@ -0,0 +1,18 @@
+import { reducer, initialState as snapshot } from './editor.reducer';
+import { EditorActionTypes, ClearEditor } from '../action/editor.action';
+
+describe('Provider Editor Reducer', () => {
+ describe('undefined action', () => {
+ it('should return the default state', () => {
+ const result = reducer(snapshot, {} as any);
+
+ expect(result).toEqual(snapshot);
+ });
+ });
+
+ describe(`${EditorActionTypes.CLEAR}`, () => {
+ it('should reset to initial state', () => {
+ expect(reducer(snapshot, new ClearEditor())).toEqual(snapshot);
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.ts
index 351a17917..609f707f0 100644
--- a/ui/src/app/metadata/provider/reducer/editor.reducer.ts
+++ b/ui/src/app/metadata/provider/reducer/editor.reducer.ts
@@ -24,6 +24,11 @@ export function reducer(state = initialState, action: EditorActionUnion): Editor
type: action.payload
};
}
+ case EditorActionTypes.CLEAR: {
+ return {
+ ...initialState
+ };
+ }
case EditorActionTypes.UPDATE_STATUS: {
return {
...state,
diff --git a/ui/src/app/metadata/provider/reducer/entity.reducer.spec.ts b/ui/src/app/metadata/provider/reducer/entity.reducer.spec.ts
new file mode 100644
index 000000000..3079a99cd
--- /dev/null
+++ b/ui/src/app/metadata/provider/reducer/entity.reducer.spec.ts
@@ -0,0 +1,18 @@
+import { reducer, initialState as snapshot } from './entity.reducer';
+import { EntityActionTypes, ClearProvider } from '../action/entity.action';
+
+describe('Provider Editor Reducer', () => {
+ describe('undefined action', () => {
+ it('should return the default state', () => {
+ const result = reducer(snapshot, {} as any);
+
+ expect(result).toEqual(snapshot);
+ });
+ });
+
+ describe(`${EntityActionTypes.CLEAR_PROVIDER}`, () => {
+ it('should reset to initial state', () => {
+ expect(reducer(snapshot, new ClearProvider())).toEqual(snapshot);
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/reducer/entity.reducer.ts b/ui/src/app/metadata/provider/reducer/entity.reducer.ts
index 101674749..5aa7eceb1 100644
--- a/ui/src/app/metadata/provider/reducer/entity.reducer.ts
+++ b/ui/src/app/metadata/provider/reducer/entity.reducer.ts
@@ -15,14 +15,20 @@ export const initialState: EntityState = {
export function reducer(state = initialState, action: EntityActionUnion): EntityState {
switch (action.type) {
+ case EntityActionTypes.CLEAR_PROVIDER: {
+ return {
+ ...initialState
+ };
+ }
case EntityActionTypes.RESET_CHANGES: {
return {
...state,
- changes: initialState.changes
+ changes: {
+ ...initialState.changes
+ }
};
}
- case EntityActionTypes.SELECT_PROVIDER:
- case EntityActionTypes.CREATE_PROVIDER: {
+ case EntityActionTypes.SELECT_PROVIDER: {
return {
...state,
base: {
@@ -39,23 +45,6 @@ export function reducer(state = initialState, action: EntityActionUnion): Entity
}
};
}
- case EntityActionTypes.SAVE_PROVIDER_REQUEST: {
- return {
- ...state,
- saving: true
- };
- }
- case EntityActionTypes.SAVE_PROVIDER_SUCCESS: {
- return {
- ...initialState,
- };
- }
- case EntityActionTypes.SAVE_PROVIDER_FAIL: {
- return {
- ...state,
- saving: false
- };
- }
default: {
return state;
}
diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts
index c736201eb..ee98fec25 100644
--- a/ui/src/app/metadata/provider/reducer/index.ts
+++ b/ui/src/app/metadata/provider/reducer/index.ts
@@ -2,15 +2,20 @@ import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromRoot from '../../../app.reducer';
import * as fromEditor from './editor.reducer';
import * as fromEntity from './entity.reducer';
+import * as fromCollection from './collection.reducer';
+import * as utils from '../../domain/domain.util';
+import { MetadataProvider } from '../../domain/model';
export interface ProviderState {
editor: fromEditor.EditorState;
entity: fromEntity.EntityState;
+ collection: fromCollection.CollectionState;
}
export const reducers = {
editor: fromEditor.reducer,
- entity: fromEntity.reducer
+ entity: fromEntity.reducer,
+ collection: fromCollection.reducer
};
export interface State extends fromRoot.State {
@@ -21,9 +26,11 @@ export const getProviderState = createFeatureSelector('provider')
export const getEditorStateFn = (state: ProviderState) => state.editor;
export const getEntityStateFn = (state: ProviderState) => state.entity;
+export const getCollectionStateFn = (state: ProviderState) => state.collection;
export const getEditorState = createSelector(getProviderState, getEditorStateFn);
export const getEntityState = createSelector(getProviderState, getEntityStateFn);
+export const getCollectionState = createSelector(getProviderState, getCollectionStateFn);
/*
Editor State
@@ -43,4 +50,16 @@ Entity State
export const getEntityIsSaved = createSelector(getEntityState, fromEntity.isEntitySaved);
export const getEntityChanges = createSelector(getEntityState, fromEntity.getEntityChanges);
export const getEntityIsSaving = createSelector(getEntityState, fromEntity.isEditorSaving);
-export const getUpdatedEntity = createSelector(getEntityState, fromEntity.getUpdatedEntity);
\ No newline at end of file
+export const getUpdatedEntity = createSelector(getEntityState, fromEntity.getUpdatedEntity);
+
+/*
+ * Select pieces of Provider Collection
+*/
+export const getAllProviders = createSelector(getCollectionState, fromCollection.selectAllProviders);
+export const getProviderEntities = createSelector(getCollectionState, fromCollection.selectProviderEntities);
+export const getSelectedProviderId = createSelector(getCollectionState, fromCollection.getSelectedProviderId);
+export const getSelectedProvider = createSelector(getProviderEntities, getSelectedProviderId, utils.getInCollectionFn);
+export const getProviderIds = createSelector(getCollectionState, fromCollection.selectProviderIds);
+export const getProviderCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded);
+
+export const getProviderNames = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.name));
diff --git a/ui/src/app/schema-form/registry.ts b/ui/src/app/schema-form/registry.ts
index 85e7d3a7d..d197dcf2c 100644
--- a/ui/src/app/schema-form/registry.ts
+++ b/ui/src/app/schema-form/registry.ts
@@ -4,17 +4,17 @@ import { CustomStringComponent } from './widget/text/string.component';
import { WidgetRegistry } from 'ngx-schema-form';
-import { ArrayWidget } from 'ngx-schema-form';
import { ButtonWidget } from 'ngx-schema-form';
-import { CheckboxWidget } from 'ngx-schema-form';
import { FileWidget } from 'ngx-schema-form';
import { IntegerWidget } from 'ngx-schema-form';
import { ObjectWidget } from 'ngx-schema-form';
import { RadioWidget } from 'ngx-schema-form';
import { RangeWidget } from 'ngx-schema-form';
-import { TextAreaWidget } from 'ngx-schema-form';
import { CustomSelectComponent } from './widget/select/select.component';
import { DatalistComponent } from './widget/datalist/datalist.component';
+import { CustomCheckboxComponent } from './widget/check/checkbox.component';
+import { CustomTextAreaComponent } from './widget/textarea/textarea.component';
+import { CustomArrayComponent } from './widget/array/array.component';
export class CustomWidgetRegistry extends WidgetRegistry {
@@ -34,26 +34,27 @@ export class CustomWidgetRegistry extends WidgetRegistry {
this.register('time', CustomStringComponent);
this.register('boolean-radio', BooleanRadioComponent);
+
this.register('fieldset', FieldsetComponent);
+ this.register('array', CustomArrayComponent);
this.register('select', CustomSelectComponent);
+ this.register('boolean', CustomCheckboxComponent);
+ this.register('checkbox', CustomCheckboxComponent);
+
+ this.register('textarea', CustomTextAreaComponent);
this.register('datalist', DatalistComponent);
/* NGX-Form */
- this.register('array', ArrayWidget);
this.register('object', ObjectWidget);
this.register('integer', IntegerWidget);
this.register('number', IntegerWidget);
this.register('range', RangeWidget);
- this.register('textarea', TextAreaWidget);
-
this.register('file', FileWidget);
this.register('radio', RadioWidget);
- this.register('boolean', CheckboxWidget);
- this.register('checkbox', CheckboxWidget);
this.register('button', ButtonWidget);
diff --git a/ui/src/app/schema-form/schema-form.module.ts b/ui/src/app/schema-form/schema-form.module.ts
index 95549b7de..d51ae2269 100644
--- a/ui/src/app/schema-form/schema-form.module.ts
+++ b/ui/src/app/schema-form/schema-form.module.ts
@@ -11,13 +11,19 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
import { SharedModule } from '../shared/shared.module';
import { CustomSelectComponent } from './widget/select/select.component';
import { DatalistComponent } from './widget/datalist/datalist.component';
+import { CustomCheckboxComponent } from './widget/check/checkbox.component';
+import { CustomTextAreaComponent } from './widget/textarea/textarea.component';
+import { CustomArrayComponent } from './widget/array/array.component';
export const COMPONENTS = [
BooleanRadioComponent,
FieldsetComponent,
CustomStringComponent,
CustomSelectComponent,
- DatalistComponent
+ DatalistComponent,
+ CustomCheckboxComponent,
+ CustomTextAreaComponent,
+ CustomArrayComponent
];
@NgModule({
diff --git a/ui/src/app/schema-form/widget/array/array.component.html b/ui/src/app/schema-form/widget/array/array.component.html
new file mode 100644
index 000000000..577c084f9
--- /dev/null
+++ b/ui/src/app/schema-form/widget/array/array.component.html
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/ui/src/app/schema-form/widget/array/array.component.ts b/ui/src/app/schema-form/widget/array/array.component.ts
new file mode 100644
index 000000000..52987cda7
--- /dev/null
+++ b/ui/src/app/schema-form/widget/array/array.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+import { ArrayWidget } from 'ngx-schema-form';
+
+@Component({
+ selector: 'array-component',
+ templateUrl: `./array.component.html`
+})
+export class CustomArrayComponent extends ArrayWidget {}
diff --git a/ui/src/app/schema-form/widget/check/checkbox.component.html b/ui/src/app/schema-form/widget/check/checkbox.component.html
new file mode 100644
index 000000000..f8f94af1a
--- /dev/null
+++ b/ui/src/app/schema-form/widget/check/checkbox.component.html
@@ -0,0 +1,37 @@
+
diff --git a/ui/src/app/schema-form/widget/check/checkbox.component.ts b/ui/src/app/schema-form/widget/check/checkbox.component.ts
new file mode 100644
index 000000000..ebb784d71
--- /dev/null
+++ b/ui/src/app/schema-form/widget/check/checkbox.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+import { CheckboxWidget } from 'ngx-schema-form';
+
+@Component({
+ selector: 'checkbox-component',
+ templateUrl: `./checkbox.component.html`
+})
+export class CustomCheckboxComponent extends CheckboxWidget { }
diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html
index 831a7e852..7b4bf8173 100644
--- a/ui/src/app/schema-form/widget/select/select.component.html
+++ b/ui/src/app/schema-form/widget/select/select.component.html
@@ -1,7 +1,7 @@
-
\ No newline at end of file
diff --git a/ui/src/app/schema-form/widget/text/string.component.ts b/ui/src/app/schema-form/widget/text/string.component.ts
index 26c1f5c44..cf46aa6e2 100644
--- a/ui/src/app/schema-form/widget/text/string.component.ts
+++ b/ui/src/app/schema-form/widget/text/string.component.ts
@@ -1,18 +1,9 @@
import { Component } from '@angular/core';
-import { ControlWidget } from 'ngx-schema-form';
+import { StringWidget } from 'ngx-schema-form';
@Component({
selector: 'custom-string',
templateUrl: `./string.component.html`,
styleUrls: ['../widget.component.scss']
})
-export class CustomStringComponent extends ControlWidget {
-
- getInputType() {
- if (!this.schema.widget.id || this.schema.widget.id === 'string') {
- return 'text';
- } else {
- return this.schema.widget.id;
- }
- }
-}
+export class CustomStringComponent extends StringWidget {}
diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.html b/ui/src/app/schema-form/widget/textarea/textarea.component.html
new file mode 100644
index 000000000..55260ac43
--- /dev/null
+++ b/ui/src/app/schema-form/widget/textarea/textarea.component.html
@@ -0,0 +1,21 @@
+
+
+ {{ schema.title }}
+
+ {{ schema.description }}
+
+
+
+ {{schema.description}}
+
+
\ No newline at end of file
diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.ts b/ui/src/app/schema-form/widget/textarea/textarea.component.ts
new file mode 100644
index 000000000..6586d7d1f
--- /dev/null
+++ b/ui/src/app/schema-form/widget/textarea/textarea.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+import { TextAreaWidget } from 'ngx-schema-form';
+
+@Component({
+ selector: 'textarea-component',
+ templateUrl: `./textarea.component.html`
+})
+export class CustomTextAreaComponent extends TextAreaWidget {}
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 4af6af8ce..f166ad254 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -35,6 +35,7 @@ import { PrettyXml } from './pipe/pretty-xml.pipe';
InputDefaultsDirective,
I18nTextComponent,
ValidFormIconComponent,
+ ValidationClassDirective,
InfoLabelDirective
]
})
diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts
index b144601ae..ecb507cb4 100644
--- a/ui/src/app/wizard/action/wizard.action.ts
+++ b/ui/src/app/wizard/action/wizard.action.ts
@@ -7,8 +7,12 @@ export enum WizardActionTypes {
UPDATE_DEFINITION = '[Wizard] Update Definition',
SET_DISABLED = '[Wizard] Set Disabled',
+ ADD_SCHEMA = '[Wizard] Add Schema',
+
NEXT = '[Wizard] Next Page',
- PREVIOUS = '[Wizard] Previous Page'
+ PREVIOUS = '[Wizard] Previous Page',
+
+ CLEAR = '[Wizard] Clear'
}
export class SetIndex implements Action {
@@ -47,10 +51,22 @@ export class Previous implements Action {
constructor(public payload: string) { }
}
+export class AddSchema implements Action {
+ readonly type = WizardActionTypes.ADD_SCHEMA;
+
+ constructor(public payload: { id: string, schema: any }) { }
+}
+
+export class ClearWizard implements Action {
+ readonly type = WizardActionTypes.CLEAR;
+}
+
export type WizardActionUnion =
| SetIndex
| SetDefinition
| UpdateDefinition
| SetDisabled
| Next
- | Previous;
+ | Previous
+ | ClearWizard
+ | AddSchema;
diff --git a/ui/src/app/wizard/component/wizard.component.html b/ui/src/app/wizard/component/wizard.component.html
index e5921c6ac..3d62f7d7a 100644
--- a/ui/src/app/wizard/component/wizard.component.html
+++ b/ui/src/app/wizard/component/wizard.component.html
@@ -8,21 +8,26 @@
Back
- {{ (previous$ | async).index }}. {{ (previous$ | async).label }}
+ {{ (previous$ | async).index }}.
+ {{ (previous$ | async).label }}
- {{ (current$ | async).index }}
- {{ (current$ | async).index }}. {{ (current$ | async).label }}
+ {{ (current$ | async).index }}
+
+ {{ (current$ | async).index }}.
+
+ {{ (current$ | async).label }}
-
-
-
diff --git a/ui/src/app/wizard/component/wizard.component.ts b/ui/src/app/wizard/component/wizard.component.ts
index 5ab006519..da9cdafb0 100644
--- a/ui/src/app/wizard/component/wizard.component.ts
+++ b/ui/src/app/wizard/component/wizard.component.ts
@@ -13,6 +13,7 @@ import { Observable } from 'rxjs';
export class WizardComponent implements OnChanges {
@Output() onNext = new EventEmitter();
@Output() onPrevious = new EventEmitter();
+ @Output() onLast = new EventEmitter();
@Output() onSave = new EventEmitter();
currentPage: any = {};
diff --git a/ui/src/app/wizard/guard/step-exists.guard.ts b/ui/src/app/wizard/guard/step-exists.guard.ts
deleted file mode 100644
index cd382ee14..000000000
--- a/ui/src/app/wizard/guard/step-exists.guard.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Injectable } from '@angular/core';
-import {
- CanActivate,
- Router,
- ActivatedRouteSnapshot,
- RouterStateSnapshot
-} from '@angular/router';
-import { Store } from '@ngrx/store';
-import { Observable, of } from 'rxjs';
-import { map, tap } from 'rxjs/operators';
-
-import * as fromWizard from '../reducer';
-
-@Injectable()
-export class StepExistsGuard implements CanActivate {
- constructor(
- private store: Store,
- private router: Router
- ) { }
-
- canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
- this.store.select(fromWizard.getWizardDefinition).pipe(
- map(current => !!current)
- ).subscribe(defined => console.log(defined));
- return of(true);
- }
-}
-
-// !isDefined ? this.router.navigate(['metadata/provider/wizard/new']) : isDefined
diff --git a/ui/src/app/wizard/model/wizard.ts b/ui/src/app/wizard/model/wizard.ts
index bcceb9f00..8b5fe344e 100644
--- a/ui/src/app/wizard/model/wizard.ts
+++ b/ui/src/app/wizard/model/wizard.ts
@@ -1,9 +1,11 @@
-import { MetadataProvider } from '../../metadata/domain/model';
-
export interface Wizard {
label: string;
type: string;
steps: WizardStep[];
+ translate: {
+ parser(changes: Partial, schema?: any),
+ formatter(changes: Partial, schema?: any)
+ };
}
export interface WizardStep {
@@ -12,7 +14,6 @@ export interface WizardStep {
initialValues?: WizardValue[];
schema: string;
index: number;
- parser?(changes: Partial, schema: any);
}
export interface WizardValue {
diff --git a/ui/src/app/wizard/reducer/index.spec.ts b/ui/src/app/wizard/reducer/index.spec.ts
new file mode 100644
index 000000000..78ff09441
--- /dev/null
+++ b/ui/src/app/wizard/reducer/index.spec.ts
@@ -0,0 +1,91 @@
+import * as selectors from './';
+import { FileBackedHttpMetadataProviderWizard } from '../../metadata/provider/model';
+
+describe('wizard index selectors', () => {
+ describe('getSchema method', () => {
+ it('should return the schema by index name', () => {
+ expect(
+ selectors.getSchema('common', FileBackedHttpMetadataProviderWizard)
+ ).toBe(FileBackedHttpMetadataProviderWizard.steps[0].schema);
+ });
+ it('should return nothing if no schema is found', () => {
+ expect(
+ selectors.getSchema('common', null)
+ ).toBeFalsy();
+ });
+ });
+ describe('getPreviousFn method', () => {
+ it('should return the previous step', () => {
+ expect(
+ selectors.getPreviousFn('reloading', FileBackedHttpMetadataProviderWizard)
+ ).toBe(FileBackedHttpMetadataProviderWizard.steps[0]);
+ });
+ it('should return null if the index is the first step', () => {
+ expect(
+ selectors.getPreviousFn('common', FileBackedHttpMetadataProviderWizard)
+ ).toBeFalsy();
+ });
+ it('should return nothing if no schema is found', () => {
+ expect(
+ selectors.getPreviousFn('common', null)
+ ).toBeFalsy();
+ });
+ });
+
+ describe('getNextFn method', () => {
+ it('should return the previous step', () => {
+ expect(
+ selectors.getNextFn('common', FileBackedHttpMetadataProviderWizard)
+ ).toBe(FileBackedHttpMetadataProviderWizard.steps[1]);
+ });
+ it('should return null if the index is the last step', () => {
+ expect(
+ selectors.getNextFn('summary', FileBackedHttpMetadataProviderWizard)
+ ).toBeFalsy();
+ });
+ it('should return nothing if no schema is found', () => {
+ expect(
+ selectors.getNextFn('common', null)
+ ).toBeFalsy();
+ });
+ });
+
+ describe('getCurrentFn method', () => {
+ it('should return the current step', () => {
+ expect(
+ selectors.getCurrentFn('common', FileBackedHttpMetadataProviderWizard)
+ ).toBe(FileBackedHttpMetadataProviderWizard.steps[0]);
+ });
+ it('should return nothing if no schema is found', () => {
+ expect(
+ selectors.getCurrentFn('common', null)
+ ).toBeFalsy();
+ });
+ });
+
+ describe('getLastFn method', () => {
+ it('should return the last step', () => {
+ expect(
+ selectors.getLastFn('summary', FileBackedHttpMetadataProviderWizard)
+ ).toBe(FileBackedHttpMetadataProviderWizard.steps.find(step => step.id === 'summary'));
+ });
+ it('should return nothing if no definition is provided', () => {
+ expect(
+ selectors.getLastFn('common', null)
+ ).toBeFalsy();
+ });
+ it('should return nothing if no schema is found', () => {
+ expect(
+ selectors.getLastFn('common', FileBackedHttpMetadataProviderWizard)
+ ).toBeFalsy();
+ });
+ });
+
+ describe('getModelFn method', () => {
+ it('should return the model', () => {
+ const step = FileBackedHttpMetadataProviderWizard.steps.find(s => s.id === 'filters');
+ console.log(step);
+ expect(selectors.getModelFn(step)).toEqual({ metadataFilters: [] });
+ });
+ });
+});
diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts
index 1119d5d14..7e3db4c01 100644
--- a/ui/src/app/wizard/reducer/index.ts
+++ b/ui/src/app/wizard/reducer/index.ts
@@ -1,7 +1,7 @@
import * as fromRoot from '../../app.reducer';
import * as fromWizard from './wizard.reducer';
import { createFeatureSelector, createSelector } from '@ngrx/store';
-import { Wizard } from '../model';
+import { Wizard, WizardStep } from '../model';
export interface WizardState {
wizard: fromWizard.State;
@@ -22,13 +22,14 @@ export const getState = createSelector(getWizardState, getWizardStateFn);
export const getWizardIndex = createSelector(getState, fromWizard.getIndex);
export const getWizardIsDisabled = createSelector(getState, fromWizard.getDisabled);
export const getWizardDefinition = createSelector(getState, fromWizard.getDefinition);
+export const getSchemaCollection = createSelector(getState, fromWizard.getCollection);
export const getSchema = (index: string, wizard: Wizard) => {
+ if (!wizard) { return null; }
const step = wizard.steps.find(s => s.id === index);
return step ? step.schema : null;
};
-
export const getCurrentWizardSchema = createSelector(getWizardIndex, getWizardDefinition, getSchema);
export const getPreviousFn = (index: string, wizard: Wizard) => {
@@ -54,7 +55,13 @@ export const getLastFn = (index: string, wizard: Wizard) => {
return index === step.id ? step : null;
};
+export const getModelFn = (currentStep: WizardStep) => {
+ const model = (currentStep && currentStep.initialValues) ? currentStep.initialValues : [];
+ return model.reduce((m, property) => ({...m, [property.key]: property.value }), {});
+};
+
export const getPrevious = createSelector(getWizardIndex, getWizardDefinition, getPreviousFn);
export const getCurrent = createSelector(getWizardIndex, getWizardDefinition, getCurrentFn);
export const getNext = createSelector(getWizardIndex, getWizardDefinition, getNextFn);
export const getLast = createSelector(getWizardIndex, getWizardDefinition, getLastFn);
+export const getModel = createSelector(getCurrent, getModelFn);
diff --git a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts
new file mode 100644
index 000000000..c922c226d
--- /dev/null
+++ b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts
@@ -0,0 +1,74 @@
+import { reducer, initialState as snapshot } from './wizard.reducer';
+import * as selectors from './wizard.reducer';
+import { WizardActionTypes, ClearWizard, AddSchema, SetDisabled, SetDefinition, SetIndex, UpdateDefinition } from '../action/wizard.action';
+import { SCHEMA } from '../../../testing/form-schema.stub';
+import { MetadataProviderWizard, FileBackedHttpMetadataProviderWizard } from '../../metadata/provider/model';
+
+
+
+describe('Wizard Reducer', () => {
+ describe('undefined action', () => {
+ it('should return the default state', () => {
+ const result = reducer(snapshot, {} as any);
+
+ expect(result).toEqual(snapshot);
+ });
+ });
+
+ describe(`${WizardActionTypes.CLEAR}`, () => {
+ it('should reset to initial state', () => {
+ expect(reducer(snapshot, new ClearWizard())).toEqual(snapshot);
+ });
+ });
+
+ describe(`${WizardActionTypes.ADD_SCHEMA}`, () => {
+ it('should add the payload to the schema collection', () => {
+ expect(reducer(snapshot, new AddSchema({id: 'foo', schema: SCHEMA })).schemaCollection).toEqual({ 'foo': SCHEMA });
+ });
+ });
+
+ describe(`${WizardActionTypes.SET_DISABLED}`, () => {
+ it('should set the disabled property on the wizard', () => {
+ expect(reducer(snapshot, new SetDisabled(true)).disabled).toBe(true);
+ expect(reducer(snapshot, new SetDisabled(false)).disabled).toBe(false);
+ });
+ });
+
+ describe(`${WizardActionTypes.SET_DEFINITION}`, () => {
+ it('should set the definition property on the wizard', () => {
+ expect(reducer(snapshot, new SetDefinition(MetadataProviderWizard)).definition).toBe(MetadataProviderWizard);
+ });
+ });
+
+ describe(`${WizardActionTypes.SET_INDEX}`, () => {
+ it('should set the definition property on the wizard', () => {
+ expect(reducer(snapshot, new SetIndex(MetadataProviderWizard.steps[0].id)).index).toBe('new');
+ });
+ });
+
+ describe(`${WizardActionTypes.SET_INDEX}`, () => {
+ let state = reducer(snapshot, new SetDefinition(MetadataProviderWizard));
+ it('should set the definition property on the wizard', () => {
+ expect(reducer(state, new UpdateDefinition(FileBackedHttpMetadataProviderWizard))).toEqual({
+ ...state,
+ definition: {
+ ...MetadataProviderWizard,
+ ...FileBackedHttpMetadataProviderWizard,
+ steps: [
+ ...MetadataProviderWizard.steps,
+ ...FileBackedHttpMetadataProviderWizard.steps
+ ]
+ }
+ });
+ });
+ });
+
+ describe('selector functions', () => {
+ it('should return pieces of state', () => {
+ expect(selectors.getCollection(snapshot)).toEqual(snapshot.schemaCollection);
+ expect(selectors.getDefinition(snapshot)).toEqual(snapshot.definition);
+ expect(selectors.getDisabled(snapshot)).toEqual(snapshot.disabled);
+ expect(selectors.getIndex(snapshot)).toEqual(snapshot.index);
+ });
+ });
+});
diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts
index de40f86a3..9ecae96ce 100644
--- a/ui/src/app/wizard/reducer/wizard.reducer.ts
+++ b/ui/src/app/wizard/reducer/wizard.reducer.ts
@@ -5,16 +5,27 @@ export interface State {
index: string;
disabled: boolean;
definition: Wizard;
+ schemaCollection: { [id: string]: any };
}
export const initialState: State = {
index: null,
disabled: false,
- definition: null
+ definition: null,
+ schemaCollection: {}
};
export function reducer(state = initialState, action: WizardActionUnion): State {
switch (action.type) {
+ case WizardActionTypes.ADD_SCHEMA: {
+ return {
+ ...state,
+ schemaCollection: {
+ ...state.schemaCollection,
+ [action.payload.id]: action.payload.schema
+ }
+ };
+ }
case WizardActionTypes.SET_DISABLED: {
return {
...state,
@@ -56,3 +67,4 @@ export function reducer(state = initialState, action: WizardActionUnion): State
export const getIndex = (state: State) => state.index;
export const getDisabled = (state: State) => state.disabled;
export const getDefinition = (state: State) => state.definition;
+export const getCollection = (state: State) => state.schemaCollection;
diff --git a/ui/src/app/wizard/wizard.module.ts b/ui/src/app/wizard/wizard.module.ts
index 6664ec16a..8612649b6 100644
--- a/ui/src/app/wizard/wizard.module.ts
+++ b/ui/src/app/wizard/wizard.module.ts
@@ -5,7 +5,6 @@ import { EffectsModule } from '@ngrx/effects';
import { WizardComponent } from './component/wizard.component';
import { reducers } from './reducer';
-import { StepExistsGuard } from './guard/step-exists.guard';
@NgModule({
declarations: [
@@ -23,9 +22,7 @@ export class WizardModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: RootWizardModule,
- providers: [
- StepExistsGuard
- ]
+ providers: []
};
}
}
diff --git a/ui/src/assets/schema/provider/filebacked-http-common.schema.json b/ui/src/assets/schema/provider/filebacked-http-common.schema.json
index 806c7c053..3bf433b29 100644
--- a/ui/src/assets/schema/provider/filebacked-http-common.schema.json
+++ b/ui/src/assets/schema/provider/filebacked-http-common.schema.json
@@ -11,16 +11,19 @@
"useDefaultPredicateRegistry",
"satisfyAnyPredicates"
],
+ "required": ["id", "metadataURL"],
"properties": {
"id": {
"title": "ID",
"description": "Unique Identifier",
- "type": "string"
+ "type": "string",
+ "default": ""
},
"metadataURL": {
"title": "Metadata URL",
"description": "Metadata URL",
- "type": "string"
+ "type": "string",
+ "default": ""
},
"initializeFromBackupFile": {
"title": "Initialize From Backup File?",
diff --git a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json
index 2b3d32846..98ec3eed9 100644
--- a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json
+++ b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json
@@ -6,7 +6,7 @@
"description": "",
"type": "object",
"properties": {
- "@RequiredValidUntilFilter": {
+ "RequiredValidUntil": {
"title": "Required Valid Until Filter",
"type": "object",
"widget": {
@@ -15,12 +15,13 @@
"properties": {
"maxValidityInterval": {
"title": "Max Validity Interval",
- "description": "",
- "type": "number"
+ "description": "Max Validity Interval",
+ "type": "number",
+ "default": 0
}
}
},
- "@SignatureValidationFilter": {
+ "SignatureValidation": {
"title": "Signature Validation Filter",
"type": "object",
"widget": {
@@ -29,18 +30,20 @@
"properties": {
"requireSignedRoot": {
"title": "Require Signed Root",
- "description": "",
+ "description": "Require Signed Root",
"type": "boolean",
"default": true
},
"certificateFile": {
"title": "Certificate File",
- "description": "",
- "type": "string"
+ "description": "Certificate File",
+ "type": "string",
+ "widget": "textarea",
+ "default": ""
}
}
},
- "@EntityRoleWhiteListFilter": {
+ "EntityRoleWhiteList": {
"title": "Entity Role Whitelist Filter",
"type": "object",
"widget": {
@@ -49,7 +52,7 @@
"properties": {
"retainedRoles": {
"title": "Retained Roles",
- "description": "",
+ "description": "Retained Roles",
"type": "array",
"items": {
"widget": {
@@ -74,13 +77,13 @@
},
"removeRolelessEntityDescriptors": {
"title": "Remove Roleless Entity Descriptors?",
- "description": "",
+ "description": "Remove Roleless Entity Descriptors?",
"type": "boolean",
"default": true
},
"removeEmptyEntitiesDescriptors": {
"title": "Remove Empty Entities Descriptors?",
- "description": "",
+ "description": "Remove Empty Entities Descriptors?",
"type": "boolean",
"default": true
}
diff --git a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json
index 9595d904c..9fa60f808 100644
--- a/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json
+++ b/ui/src/assets/schema/provider/filebacked-http-reloading.schema.json
@@ -21,7 +21,8 @@
"PT12H",
"PT24H"
]
- }
+ },
+ "default": ""
},
"maxRefreshDelay": {
"title": "Max Refresh Delay",
@@ -40,12 +41,14 @@
"PT12H",
"PT24H"
]
- }
+ },
+ "default": ""
},
"refreshDelayFactor": {
"title": "Refresh Delay Factor",
"description": "",
- "type": "number"
+ "type": "number",
+ "default": null
},
"resolveViaPredicatesOnly": {
"title": "Resolve Via Predicates Only?",
@@ -87,7 +90,8 @@
"PT12H",
"PT24H"
]
- }
+ },
+ "default": ""
}
}
}
diff --git a/ui/src/assets/schema/provider/metadata-provider-summary.schema.json b/ui/src/assets/schema/provider/metadata-provider-summary.schema.json
new file mode 100644
index 000000000..b3cc3b73c
--- /dev/null
+++ b/ui/src/assets/schema/provider/metadata-provider-summary.schema.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "title": "Enable Metadata Provider",
+ "description": "Enable Metadata Provider",
+ "type": "boolean",
+ "default": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/assets/schema/provider/metadata-provider.schema.json b/ui/src/assets/schema/provider/metadata-provider.schema.json
index 241a57e85..f5a840f35 100644
--- a/ui/src/assets/schema/provider/metadata-provider.schema.json
+++ b/ui/src/assets/schema/provider/metadata-provider.schema.json
@@ -1,5 +1,5 @@
{
- "title": "@MetadataResolver",
+ "title": "MetadataResolver",
"type": "object",
"widget": {
"id": "fieldset"
@@ -8,7 +8,11 @@
"name": {
"title": "Metadata Provider Name (Dashboard Display Only)",
"description": "Metadata Provider Name (Dashboard Display Only)",
- "type": "string"
+ "type": "string",
+ "widget": {
+ "id": "string",
+ "help": "Must be unique."
+ }
},
"@type": {
"title": "Metadata Provider Type",
@@ -21,7 +25,7 @@
"oneOf": [
{
"enum": [
- "@FileBackedHttpMetadataProvider"
+ "FileBackedHttpMetadataResolver"
],
"description": "FileBackedHttpMetadataProvider"
}
diff --git a/ui/src/testing/form-schema.stub.ts b/ui/src/testing/form-schema.stub.ts
new file mode 100644
index 000000000..c91f63bf1
--- /dev/null
+++ b/ui/src/testing/form-schema.stub.ts
@@ -0,0 +1,48 @@
+export const SCHEMA = {
+ 'title': 'MetadataResolver',
+ 'type': 'object',
+ 'widget': {
+ 'id': 'fieldset'
+ },
+ 'properties': {
+ 'name': {
+ 'title': 'Metadata Provider Name (Dashboard Display Only)',
+ 'description': 'Metadata Provider Name (Dashboard Display Only)',
+ 'type': 'string',
+ 'widget': {
+ 'id': 'string',
+ 'help': 'Must be unique.'
+ }
+ },
+ '@type': {
+ 'title': 'Metadata Provider Type',
+ 'description': 'Metadata Provider Type',
+ 'ui:placeholder': 'Select a metadata provider type',
+ 'type': 'string',
+ 'widget': {
+ 'id': 'select'
+ },
+ 'oneOf': [
+ {
+ 'enum': [
+ 'FileBackedHttpMetadataResolver'
+ ],
+ 'description': 'FileBackedHttpMetadataProvider'
+ }
+ ]
+ }
+ },
+ 'required': [
+ 'name',
+ '@type'
+ ],
+ 'fieldsets': [
+ {
+ 'type': 'section',
+ 'fields': [
+ 'name',
+ '@type'
+ ]
+ }
+ ]
+};