From 16d1fa7a899522ce02a626ce0717bfb4c1652886 Mon Sep 17 00:00:00 2001 From: mofogasy Date: Tue, 15 Oct 2019 11:30:39 +0200 Subject: [PATCH] feat: allow querying capabilities matching a given qualifier pattern closes: #188 --- .../application-list.component.html | 5 - .../application-list.component.scss | 10 -- .../application-list.component.ts | 58 ----------- .../app/dev-tools/dev-tools-routing.module.ts | 5 +- .../src/app/dev-tools/dev-tools.module.ts | 4 +- .../dev-tools/dev-tools.component.html | 54 +++++++++++ .../dev-tools/dev-tools.component.scss | 79 +++++++++++++++ .../dev-tools/dev-tools.component.ts | 96 +++++++++++++++++++ .../dev-tools-app/src/assets/manifest.json | 13 +++ .../src/lib/core/qualifier-patcher.ts | 22 ++--- ...anifest-registry-intent-handler.service.ts | 10 +- 11 files changed, 264 insertions(+), 92 deletions(-) delete mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html delete mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss delete mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts create mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.html create mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.scss create mode 100644 projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.ts diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html deleted file mode 100644 index 213303c39..000000000 --- a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss deleted file mode 100644 index 57bda86d3..000000000 --- a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'common'; - -:host { - display: flex; - padding: app-padding(); - - > sci-list { - flex: auto; - } -} diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts deleted file mode 100644 index f5fa7967c..000000000 --- a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2018-2019 Swiss Federal Railways - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { Component, TrackByFunction } from '@angular/core'; -import { BehaviorSubject, combineLatest, MonoTypeOperatorFunction, Observable, OperatorFunction } from 'rxjs'; -import { Manifest, ManifestRegistryService } from '@scion/workbench-application.core'; -import { map } from 'rxjs/operators'; -import { toFilterRegExp } from '@scion/app/common'; - -@Component({ - selector: 'app-application-list', - templateUrl: './application-list.component.html', - styleUrls: ['./application-list.component.scss'], -}) -export class ApplicationListComponent { - - private _filter$ = new BehaviorSubject(null); - - public manifests$: Observable; - - constructor(private _manifestRegistryService: ManifestRegistryService) { - this.manifests$ = combineLatest([this._filter$, this._manifestRegistryService.manifests$]) - .pipe( - filterManifests(), - sortManifests(), - ); - } - - public onFilter(filterText: string): void { - this._filter$.next(filterText); - } - - public trackByFn: TrackByFunction = (index: number, manifest: Manifest): any => { - return manifest.symbolicName; - }; -} - -function filterManifests(): OperatorFunction<[string, Manifest[]], Manifest[]> { - return map(([filter, manifests]: [string, Manifest[]]): Manifest[] => { - if (!filter) { - return manifests; - } - - const filterRegExp = toFilterRegExp(filter); - return manifests.filter(manifest => filterRegExp.test(manifest.name) || filterRegExp.test(manifest.symbolicName)); - }); -} - -function sortManifests(): MonoTypeOperatorFunction { - return map((manifests: Manifest[]): Manifest[] => [...manifests].sort((mf1, mf2) => mf1.name.localeCompare(mf2.name))); -} diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts index b1a0ec720..9340a0994 100644 --- a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts +++ b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts @@ -10,12 +10,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { ApplicationListComponent } from './application-list/application-list.component'; +import { DevToolsComponent } from './dev-tools/dev-tools.component'; import { ApplicationViewComponent } from './application-view/application-view.component'; import { OutletCapabilityExecPopupComponent } from './outlet-capability-exec-popup/outlet-capability-exec-popup.component'; const routes: Routes = [ - {path: 'application-list', component: ApplicationListComponent}, + {path: '', component: DevToolsComponent}, + {path: 'application-list', component: DevToolsComponent}, {path: 'application/:symbolicName', component: ApplicationViewComponent}, {path: 'view-capability/:id', component: OutletCapabilityExecPopupComponent}, {path: 'popup-capability/:id', component: OutletCapabilityExecPopupComponent}, diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts index 12cd4a7cd..c5b9c5149 100644 --- a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts +++ b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts @@ -15,7 +15,7 @@ import { SciAccordionModule, SciFilterFieldModule, SciListModule, SciParamsEnter import { DevToolsRoutingModule } from './dev-tools-routing.module'; import { ReactiveFormsModule } from '@angular/forms'; import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; -import { ApplicationListComponent } from './application-list/application-list.component'; +import { DevToolsComponent } from './dev-tools/dev-tools.component'; import { ApplicationListItemComponent } from './application-list-item/application-list-item.component'; import { ApplicationViewComponent } from './application-view/application-view.component'; import { CapabilityAccordionItemComponent } from './capability-accordion-item/capability-accordion-item.component'; @@ -27,7 +27,7 @@ import { OutletCapabilityExecPopupComponent } from './outlet-capability-exec-pop @NgModule({ declarations: [ - ApplicationListComponent, + DevToolsComponent, ApplicationListItemComponent, ApplicationViewComponent, CapabilityAccordionItemComponent, diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.html b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.html new file mode 100644 index 000000000..92fe39255 --- /dev/null +++ b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.html @@ -0,0 +1,54 @@ +
+
+

Application List

+ +
+ + + + + + + + +
+ +
+
+

Lookup Capabilities

+ +
+ + + + + + + +

Capabilities matching the type and qualifier:

+ + + + + + + + + + + + + No capabilities found. + +
+
+
+ + + diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.scss b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.scss new file mode 100644 index 000000000..33dec8a21 --- /dev/null +++ b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.scss @@ -0,0 +1,79 @@ +@import 'common'; + +:host { + display: flex; + flex-direction: column; + padding: app-padding(); + + > section { + flex: 1 1 0; + display: flex; + flex-direction: column; + overflow: hidden; + + &:not(:first-of-type) { + margin-top: 1.75em; + } + + &:not(.expanded) { + flex: none; + } + + > header { + flex: none; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + + > h2 { + flex: auto; + font-size: 1.25em; + margin-top: 0; + } + + > span.toggle { + flex: none; + margin-left: .5em; + user-select: none; + + &::after { + content: 'expand_more' + } + } + } + + &.expanded > header > span.toggle::after { + content: 'expand_less'; + } + + &:not(.expanded) > sci-viewport { + height: 0; + } + + > sci-viewport.app-list, sci-viewport.lookup-capabilities { + flex: auto; + + &.lookup-capabilities { + --grid-template-rows: min-content min-content min-content 1fr; + --gap: .5em 0; + + output { + flex: auto; + margin-top: 1em; + display: flex; + flex-direction: column; + + > h3 { + flex: none; + } + + > sci-accordion { + flex: auto; + min-height: 10em; + } + } + } + } + } +} diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.ts b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.ts new file mode 100644 index 000000000..823542f98 --- /dev/null +++ b/projects/app/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools/dev-tools.component.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018-2019 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, TrackByFunction } from '@angular/core'; +import { BehaviorSubject, combineLatest, MonoTypeOperatorFunction, Observable, OperatorFunction } from 'rxjs'; +import { PlatformCapabilityTypes, Capability, Manifest, ManifestRegistryService, Qualifier } from '@scion/workbench-application.core'; +import { map } from 'rxjs/operators'; +import { PARAM_NAME, PARAM_VALUE, SciParamsEnterComponent, toFilterRegExp } from '@scion/app/common'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; + +const CAPABILITY_LOOKUP_TYPE = 'type'; +const CAPABILITY_LOOKUP_QUALIFIER = 'qualifier'; + +@Component({ + selector: 'app-dev-tools', + templateUrl: './dev-tools.component.html', + styleUrls: ['./dev-tools.component.scss'], +}) +export class DevToolsComponent { + + public readonly CAPABILITY_LOOKUP_TYPE = CAPABILITY_LOOKUP_TYPE; + public readonly CAPABILITY_LOOKUP_QUALIFIER = CAPABILITY_LOOKUP_QUALIFIER; + + public readonly PlatformCapabilityTypes = PlatformCapabilityTypes; + + public form: FormGroup; + public manifests$: Observable; + + public appListPanelExpanded = true; + public capabilityLookupPanelExpanded = false; + public capabilityLookupResult$: Observable; + + private _appListFilter$ = new BehaviorSubject(null); + + constructor(formBuilder: FormBuilder, private _manifestRegistryService: ManifestRegistryService) { + this.form = formBuilder.group({ + [CAPABILITY_LOOKUP_TYPE]: formBuilder.control([], Validators.required), + [CAPABILITY_LOOKUP_QUALIFIER]: formBuilder.array([ + formBuilder.group({ + [PARAM_NAME]: formBuilder.control('*'), + [PARAM_VALUE]: formBuilder.control('*'), + }) + ]), + }); + + this.manifests$ = combineLatest([this._appListFilter$, this._manifestRegistryService.manifests$]) + .pipe( + filterManifests(), + sortManifests(), + ); + } + + public onLookupClick(): void { + const type = this.form.get(CAPABILITY_LOOKUP_TYPE).value; + const qualifier: Qualifier = SciParamsEnterComponent.toParams(this.form.get(CAPABILITY_LOOKUP_QUALIFIER) as FormArray); + this.capabilityLookupResult$ = this._manifestRegistryService.capabilities$(type, qualifier); + } + + public toggleAppListPanel(): void { + this.appListPanelExpanded = !this.appListPanelExpanded; + } + + public toggleCapabilityLookupPanel(): void { + this.capabilityLookupPanelExpanded = !this.capabilityLookupPanelExpanded; + } + + public onFilter(filterText: string): void { + this._appListFilter$.next(filterText); + } + + public trackByFn: TrackByFunction = (index: number, manifest: Manifest): any => { + return manifest.symbolicName; + }; +} + +function filterManifests(): OperatorFunction<[string, Manifest[]], Manifest[]> { + return map(([filter, manifests]: [string, Manifest[]]): Manifest[] => { + if (!filter) { + return manifests; + } + + const filterRegExp = toFilterRegExp(filter); + return manifests.filter(manifest => filterRegExp.test(manifest.name) || filterRegExp.test(manifest.symbolicName)); + }); +} + +function sortManifests(): MonoTypeOperatorFunction { + return map((manifests: Manifest[]): Manifest[] => [...manifests].sort((mf1, mf2) => mf1.name.localeCompare(mf2.name))); +} diff --git a/projects/app/workbench-application-platform/dev-tools-app/src/assets/manifest.json b/projects/app/workbench-application-platform/dev-tools-app/src/assets/manifest.json index 715973ec6..6c6f2ae32 100644 --- a/projects/app/workbench-application-platform/dev-tools-app/src/assets/manifest.json +++ b/projects/app/workbench-application-platform/dev-tools-app/src/assets/manifest.json @@ -28,6 +28,19 @@ "title": "Application manifests" } }, + { + "type": "view", + "qualifier": { + "entity": "dev-tools" + }, + "private": false, + "description": "Provides DevTools for workbench application platform.", + "properties": { + "path": "dev-tools", + "title": "DevTools", + "heading": "Workbench Application Platform" + } + }, { "type": "view", "qualifier": { diff --git a/projects/scion/workbench-application-platform/src/lib/core/qualifier-patcher.ts b/projects/scion/workbench-application-platform/src/lib/core/qualifier-patcher.ts index 3bb0997f5..a72608a27 100644 --- a/projects/scion/workbench-application-platform/src/lib/core/qualifier-patcher.ts +++ b/projects/scion/workbench-application-platform/src/lib/core/qualifier-patcher.ts @@ -11,32 +11,30 @@ import { NilQualifier, Qualifier } from '@scion/workbench-application-platform.api'; /** - * Returns a copy of the given intent qualifier with all its wildcard qualifier values - * replaced with values of the given capability qualifier, if any. - * Also, if the intent qualifier specifies a wildcard key, it is merged with the capability qualifier. + * Returns a copy of the given qualifier with all its wildcard qualifier values replaced with values of the given capability qualifier, if any. + * Also, if the qualifier specifies a wildcard key, it is merged with the capability qualifier. * - * @param intentQualifier - * qualifier for an intent as specified in the manifest, may contain wildcards as qualifier key (*) - * and/or qualifier value (* or ?). + * @param qualifier + * qualifier, which may contain wildcards as qualifier key (*) and/or qualifier value (* or ?). * @param capabilityQualifier * qualifier for a capability as specified in the manifest, may contain wildcards (* or ?) as qualifier value; * if `null`, {NilQualifier} is used. */ -export function patchQualifier(intentQualifier: Qualifier, capabilityQualifier: Qualifier): Qualifier { - if (!intentQualifier || !capabilityQualifier) { - return intentQualifier || NilQualifier; +export function patchQualifier(qualifier: Qualifier, capabilityQualifier: Qualifier): Qualifier { + if (!qualifier || !capabilityQualifier) { + return qualifier || NilQualifier; } // Create a working copy of the intent qualifier - const _intentQualifier: Qualifier = {...intentQualifier}; + const _intentQualifier: Qualifier = {...qualifier}; delete _intentQualifier['*']; Object.keys(capabilityQualifier) .forEach(key => { - if (intentQualifier[key] === '*' || intentQualifier[key] === '?') { + if (qualifier[key] === '*' || qualifier[key] === '?') { _intentQualifier[key] = capabilityQualifier[key]; } - else if (intentQualifier.hasOwnProperty('*') && !intentQualifier.hasOwnProperty(key)) { + else if (qualifier.hasOwnProperty('*') && !qualifier.hasOwnProperty(key)) { _intentQualifier[key] = capabilityQualifier[key]; } }); diff --git a/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts b/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts index 58f58fda2..a243fbe62 100644 --- a/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts +++ b/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts @@ -125,15 +125,19 @@ export class ManifestRegistryIntentHandler implements IntentHandler { /** * Finds capabilities of given type and qualifier. * - * There are ony capabilities returned for which the requesting application has manifested an intent. + * There are only capabilities returned for which the requesting application has manifested an intent. */ private queryCapabilities(envelope: MessageEnvelope): void { const type: string = envelope.message.payload.type; const qualifier: Qualifier = envelope.message.payload.qualifier; - const capabilities: Capability[] = this._manifestRegistry.getCapabilities(type, qualifier) + const capabilities: Capability[] = this._manifestRegistry.getCapabilitiesByType(type) .filter(capability => this._manifestRegistry.isVisibleForApplication(capability, envelope.sender)) - .filter(capability => this._manifestRegistry.hasIntent(envelope.sender, capability.type, capability.qualifier)); + .filter(capability => { + const patchedQualifier: Qualifier = patchQualifier(qualifier, capability.qualifier); + return matchesCapabilityQualifier(capability.qualifier, patchedQualifier); + }) + .filter(capability => this._manifestRegistry.isScopeCheckDisabled(envelope.sender) || this._manifestRegistry.hasIntent(envelope.sender, capability.type, capability.qualifier)); this._messageBus.publishReply(capabilities, envelope.sender, envelope.replyToUid); }