diff --git a/e2e/example.spec.ts b/e2e/example.spec.ts index 2f97aca..2d4ca60 100644 --- a/e2e/example.spec.ts +++ b/e2e/example.spec.ts @@ -1,15 +1,12 @@ import { expect, test } from '@playwright/test'; -test.beforeEach(async ({page})=>{ - await page.goto('/') +test.beforeEach(async ({ page }) => { + await page.goto('/'); await expect(page.locator('div').nth(2)).toContainClass('bg-green-400'); -}) - +}); test('has title', async ({ page }) => { await page.goto('/'); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Showbridge/); }); - - diff --git a/e2e/modules.spec.ts b/e2e/modules.spec.ts index 55d27d8..c5ac66f 100644 --- a/e2e/modules.spec.ts +++ b/e2e/modules.spec.ts @@ -1,7 +1,7 @@ -import { expect, test } from '@playwright/test' +import { expect, test } from '@playwright/test'; test('add module', async ({ page }) => { - await page.goto('/'); - await page.locator('app-module-list').getByText('add').click(); - await page.getByRole('menuitem', { name: 'HTTP Server' }).click(); -}) \ No newline at end of file + await page.goto('/'); + await page.locator('app-module-list').getByText('add').click(); + await page.getByRole('menuitem', { name: 'HTTP Server' }).click(); +}); diff --git a/playwright.config.ts b/playwright.config.ts index 6b8ddac..16e48e7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -23,11 +23,11 @@ export default defineConfig({ reporter: 'html', webServer: [ { - name: "showbridge", - command: "showbridge --config config.yaml", - url: "http://localhost:8080/health", - timeout: 10000 - } + name: 'showbridge', + command: 'showbridge --config config.yaml', + url: 'http://localhost:8080/health', + timeout: 10000, + }, ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a670e2f..45c753e 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,7 +1,5 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; export const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - ], + providers: [provideBrowserGlobalErrorListeners()], }; diff --git a/src/app/app.html b/src/app/app.html index f24d06d..3e1cc73 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,7 +1,7 @@
- @if(eventsService.status() === 'open') { + @if (eventsService.status() === 'open') {
} @else if (eventsService.status() === 'closed') {
@@ -10,18 +10,23 @@ } showbridge
- - + +
@if (configService.pendingConfigIsValid()) { - } @else { @@ -34,11 +39,18 @@ }
- @if (eventsService.status() === 'open' && schemaService.schemasLoaded()) { + @if (eventsService.status() === 'open' && schemaService.schemasLoaded()) {
- - + +
diff --git a/src/app/app.ts b/src/app/app.ts index 7b0ccf8..b2e92c6 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -28,13 +28,12 @@ import { ConfigPreviewComponent } from './components/config-preview/config-previ MatTooltipModule, ModuleListComponent, RouteListComponent, - ConfigPreviewComponent -], + ConfigPreviewComponent, + ], templateUrl: './app.html', styleUrl: './app.css', }) export class App { - config = computed(() => this.configService.currentlyShownConfig()); modules = computed(() => this.config()?.modules ?? []); @@ -47,7 +46,7 @@ export class App { } return []; }); - + public schemaService = inject(SchemaService); public configService = inject(ConfigService); diff --git a/src/app/components/array-form/array-form.ts b/src/app/components/array-form/array-form.ts index 7b0a729..46a0f25 100644 --- a/src/app/components/array-form/array-form.ts +++ b/src/app/components/array-form/array-form.ts @@ -31,10 +31,7 @@ export class ArrayFormComponent implements OnInit { ngOnInit(): void { if (this.paramFormControl && this.paramInfo?.schema) { if (!Array.isArray(this.paramFormControl.value)) { - this.arrayValue = parseStringToArray( - this.paramFormControl.value, - this.paramInfo.schema, - ); + this.arrayValue = parseStringToArray(this.paramFormControl.value, this.paramInfo.schema); } else { this.arrayValue = this.paramFormControl.value; } @@ -101,7 +98,7 @@ export class ArrayFormComponent implements OnInit { ); } } - + // NOTE(jwetzel): this is only needed for object item types updateItem(index: number, value: any) { if (this.arrayValue) { diff --git a/src/app/components/config-preview/config-preview.ts b/src/app/components/config-preview/config-preview.ts index e0ab722..9d3fd8d 100644 --- a/src/app/components/config-preview/config-preview.ts +++ b/src/app/components/config-preview/config-preview.ts @@ -2,17 +2,13 @@ import { JsonPipe } from '@angular/common'; import { Component, computed, input } from '@angular/core'; import * as yaml from 'js-yaml'; import { Config } from '../../models/config'; -import { MatIcon, MatIconModule } from "@angular/material/icon"; +import { MatIcon, MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-config-preview', - imports: [ - MatIconModule, - MatButtonModule, - MatTooltipModule -], + imports: [MatIconModule, MatButtonModule, MatTooltipModule], templateUrl: './config-preview.html', styleUrl: './config-preview.css', }) diff --git a/src/app/components/module-list/module-list.ts b/src/app/components/module-list/module-list.ts index 3e989de..1dd0409 100644 --- a/src/app/components/module-list/module-list.ts +++ b/src/app/components/module-list/module-list.ts @@ -10,13 +10,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-module-list', - imports: [ - MatMenuModule, - MatIconModule, - MatButtonModule, - MatTooltipModule, - ModuleComponent - ], + imports: [MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule, ModuleComponent], templateUrl: './module-list.html', styleUrl: './module-list.css', }) diff --git a/src/app/components/module/module.html b/src/app/components/module/module.html index 22e8e25..1eb3a1b 100644 --- a/src/app/components/module/module.html +++ b/src/app/components/module/module.html @@ -1,49 +1,56 @@ @if (module() && schema()) { -
-
-
- keyboard_arrow_down - keyboard_arrow_up -
-
- {{ schema().title || module()?.type }} -
-
-
- close -
+
+
+
+ keyboard_arrow_down + keyboard_arrow_up
-
-
-
-
-
- - - abc - @if (!formGroup.controls['id'].valid) { - -
{{ formGroup.controls['id'].errors | json }}
- } -
- +
+ {{ schema().title || module()?.type }} +
+
+
+ close +
+
+
+
+ +
+
+ + + abc + @if (!formGroup.controls['id'].valid) { + +
{{ formGroup.controls['id'].errors | json }}
+ }
- -
-
+ +
+ +
+
@if (moduleConfigErrors().length > 0) {
@@ -58,5 +65,5 @@
}
-
+
} diff --git a/src/app/components/params-form/params-form.spec.ts b/src/app/components/params-form/params-form.spec.ts index 9603818..8f44db1 100644 --- a/src/app/components/params-form/params-form.spec.ts +++ b/src/app/components/params-form/params-form.spec.ts @@ -1,95 +1,95 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ParamsFormComponent } from './params-form'; import { SchemaService } from '../../services/schema'; import { SomeJSONSchema } from 'ajv/dist/types/json-schema'; -describe('ParamsForm', ()=>{ - let component: ParamsFormComponent; - let fixture: ComponentFixture; +describe('ParamsForm', () => { + let component: ParamsFormComponent; + let fixture: ComponentFixture; - beforeEach(async ()=>{ - fixture = TestBed.createComponent(ParamsFormComponent); - component = fixture.componentInstance; - await fixture.whenStable(); - }) + beforeEach(async () => { + fixture = TestBed.createComponent(ParamsFormComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); - it('should create', ()=>{ - expect(component).toBeTruthy(); - }) + it('should create', () => { + expect(component).toBeTruthy(); + }); - it('should create form for schema', async ()=>{ - const paramsSchema: SomeJSONSchema = { - type: "object", - properties: {}, - required: [] - } - fixture.componentRef.setInput('paramsSchema', paramsSchema) - await fixture.whenStable() - expect(fixture.nativeElement.querySelector('form')).toBeTruthy() - }) + it('should create form for schema', async () => { + const paramsSchema: SomeJSONSchema = { + type: 'object', + properties: {}, + required: [], + }; + fixture.componentRef.setInput('paramsSchema', paramsSchema); + await fixture.whenStable(); + expect(fixture.nativeElement.querySelector('form')).toBeTruthy(); + }); - it('form should have input for string property', async ()=>{ - const paramsSchema: SomeJSONSchema = { - type: "object", - properties: { - "test": { - type: "string", - } - }, - required: [] - } - fixture.componentRef.setInput('paramsSchema', paramsSchema) - await fixture.whenStable() - const formEl = fixture.nativeElement.querySelector('form') - expect(formEl).toBeDefined() - const inputEl = formEl.querySelector('input') - expect(inputEl).toBeDefined() - }) + it('form should have input for string property', async () => { + const paramsSchema: SomeJSONSchema = { + type: 'object', + properties: { + test: { + type: 'string', + }, + }, + required: [], + }; + fixture.componentRef.setInput('paramsSchema', paramsSchema); + await fixture.whenStable(); + const formEl = fixture.nativeElement.querySelector('form'); + expect(formEl).toBeDefined(); + const inputEl = formEl.querySelector('input'); + expect(inputEl).toBeDefined(); + }); - it('form should have select for string enum property', async ()=>{ - const paramsSchema: SomeJSONSchema = { - type: "object", - properties: { - "test": { - type: "string", - enum: ['one','two','three'] - } - }, - required: [] - } - fixture.componentRef.setInput('paramsSchema', paramsSchema) - await fixture.whenStable() - const formEl = fixture.nativeElement.querySelector('form') - expect(formEl).toBeDefined() - const selectEl = formEl.querySelector('select') - expect(selectEl).toBeDefined() - }) + it('form should have select for string enum property', async () => { + const paramsSchema: SomeJSONSchema = { + type: 'object', + properties: { + test: { + type: 'string', + enum: ['one', 'two', 'three'], + }, + }, + required: [], + }; + fixture.componentRef.setInput('paramsSchema', paramsSchema); + await fixture.whenStable(); + const formEl = fixture.nativeElement.querySelector('form'); + expect(formEl).toBeDefined(); + const selectEl = formEl.querySelector('select'); + expect(selectEl).toBeDefined(); + }); - it('form input should reflect data', async ()=>{ - const paramsSchema: SomeJSONSchema = { - type: "object", - properties: { - "test": { - type: "string", - } - }, - required: [] - } + it('form input should reflect data', async () => { + const paramsSchema: SomeJSONSchema = { + type: 'object', + properties: { + test: { + type: 'string', + }, + }, + required: [], + }; - fixture.componentRef.setInput('paramsSchema', paramsSchema) - await fixture.whenStable() - fixture.componentRef.setInput('data', { - test: 'hello' - }) - await fixture.whenStable() - const inputEl = fixture.nativeElement.querySelector('input') - expect(inputEl).toBeDefined() - expect(inputEl.value).toBe('hello') + fixture.componentRef.setInput('paramsSchema', paramsSchema); + await fixture.whenStable(); + fixture.componentRef.setInput('data', { + test: 'hello', + }); + await fixture.whenStable(); + const inputEl = fixture.nativeElement.querySelector('input'); + expect(inputEl).toBeDefined(); + expect(inputEl.value).toBe('hello'); - fixture.componentRef.setInput('data', { - test: 'changed' - }) - await fixture.whenStable() - expect(inputEl.value).toBe('changed') - }) -}) \ No newline at end of file + fixture.componentRef.setInput('data', { + test: 'changed', + }); + await fixture.whenStable(); + expect(inputEl.value).toBe('changed'); + }); +}); diff --git a/src/app/components/params-form/params-form.ts b/src/app/components/params-form/params-form.ts index 322a96b..114f590 100644 --- a/src/app/components/params-form/params-form.ts +++ b/src/app/components/params-form/params-form.ts @@ -1,11 +1,5 @@ import { JsonPipe } from '@angular/common'; -import { - Component, - effect, - input, - OnDestroy, - output -} from '@angular/core'; +import { Component, effect, input, OnDestroy, output } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; diff --git a/src/app/components/processor/processor.html b/src/app/components/processor/processor.html index b137e95..42f9c85 100644 --- a/src/app/components/processor/processor.html +++ b/src/app/components/processor/processor.html @@ -9,12 +9,15 @@
{{ schema()?.title || processor()?.type }}
-
+
close
@if (hasParams()) { -
+
{ if (processor !== undefined) { - if (params !== undefined){ + if (params !== undefined) { return { ...processor, params: params, @@ -45,7 +45,7 @@ export class ProcessorComponent { } return { ...processor, - } + }; } return undefined; }); diff --git a/src/app/components/route-list/route-list.html b/src/app/components/route-list/route-list.html index 124fd21..459e5cf 100644 --- a/src/app/components/route-list/route-list.html +++ b/src/app/components/route-list/route-list.html @@ -12,7 +12,7 @@
@for (route of routes(); track $index; let i = $index) {
- (); moduleIds = input(); public schemaService = inject(SchemaService); private snackBar = inject(MatSnackBar); - + deleteRoute(index: number) { this.routes.update((routes) => { if (routes) { diff --git a/src/app/components/route/route.html b/src/app/components/route/route.html index dc6102b..7143102 100644 --- a/src/app/components/route/route.html +++ b/src/app/components/route/route.html @@ -7,14 +7,19 @@ >
- chevron_right + chevron_right
Route {{ index() }}
-
+
close
-
+
@@ -58,7 +63,7 @@
Processors
@if (processors() !== undefined && processors()!.length > 0) { -
+
@for (processor of processors(); track $index; let i = $index) {
}
- } + }
diff --git a/src/app/components/route/route.ts b/src/app/components/route/route.ts index ab3dd2e..1a438a4 100644 --- a/src/app/components/route/route.ts +++ b/src/app/components/route/route.ts @@ -91,14 +91,17 @@ export class RouteComponent { }); if (this.index() !== undefined) { - this.eventsService.getRouteEventsForIndex(this.index()!).pipe( - tap((routeEvent)=>{ - this.indicatorColor.set(routeEvent.error ? 'red' : 'greenyellow'); - }), - debounceTime(100) - ).subscribe((routeEvent) => { - this.indicatorColor.set('gray') - }) + this.eventsService + .getRouteEventsForIndex(this.index()!) + .pipe( + tap((routeEvent) => { + this.indicatorColor.set(routeEvent.error ? 'red' : 'greenyellow'); + }), + debounceTime(100), + ) + .subscribe((routeEvent) => { + this.indicatorColor.set('gray'); + }); } } @@ -115,7 +118,7 @@ export class RouteComponent { this.route.update((route) => { if (route) { const processors = route.processors || []; - + processors.push(processorTemplate); return { ...route, diff --git a/src/app/models/config.ts b/src/app/models/config.ts index e685d3f..4ab71aa 100644 --- a/src/app/models/config.ts +++ b/src/app/models/config.ts @@ -1,5 +1,5 @@ export type Config = { - api: ApiConfig + api: ApiConfig; modules: ModuleConfig[]; routes: RouteConfig[]; }; @@ -31,20 +31,19 @@ export type ProcessorConfig = { params?: Record; }; - export type ModuleError = { index: number; config: ModuleConfig; error: string; -} +}; export type RouteError = { index: number; config: RouteConfig; error: string; -} +}; export type ConfigError = { moduleErrors?: ModuleError[]; routeErrors?: RouteError[]; -} \ No newline at end of file +}; diff --git a/src/app/models/events.ts b/src/app/models/events.ts index aa5b958..7e28bbf 100644 --- a/src/app/models/events.ts +++ b/src/app/models/events.ts @@ -1,19 +1,19 @@ -import { Config } from "./config" +import { Config } from './config'; export type RouterEvent = { - type: T, - data?: D, - error?: string, -} + type: T; + data?: D; + error?: string; +}; export type RouteEventData = { - index: number -} + index: number; +}; export type InputEventData = { - source: string -} + source: string; +}; export type OutputEventData = { - destination: string -} \ No newline at end of file + destination: string; +}; diff --git a/src/app/services/config.ts b/src/app/services/config.ts index b05963a..500b6d0 100644 --- a/src/app/services/config.ts +++ b/src/app/services/config.ts @@ -34,20 +34,20 @@ export class ConfigService { private http = inject(HttpClient); private settingsService = inject(SettingsService); private eventsService = inject(EventsService); - + constructor(private schemaService: SchemaService) { effect(() => { console.log('config state changed', this.currentlyShownConfig()); }); - effect(() =>{ + effect(() => { switch (this.eventsService.status()) { case 'open': console.log('Websocket connection opened, reloading config'); this.loadConfig(); break; } - }) + }); } loadConfig() { @@ -107,9 +107,9 @@ export class ConfigService { setEmptyConfig() { console.log('Setting empty config'); this.updateCurrentlyShownConfig({ - api: { + api: { enabled: true, - port: 8080 + port: 8080, }, modules: [], routes: [], diff --git a/src/app/services/events.ts b/src/app/services/events.ts index e3ec4db..2018749 100644 --- a/src/app/services/events.ts +++ b/src/app/services/events.ts @@ -1,10 +1,5 @@ import { effect, inject, Injectable, signal } from '@angular/core'; -import { - InputEventData, - OutputEventData, - RouteEventData, - RouterEvent, -} from '../models/events'; +import { InputEventData, OutputEventData, RouteEventData, RouterEvent } from '../models/events'; import { filter, Subject } from 'rxjs'; import { SettingsService } from './settings'; @Injectable({ diff --git a/src/app/services/schema.ts b/src/app/services/schema.ts index f27d8b2..7420dee 100644 --- a/src/app/services/schema.ts +++ b/src/app/services/schema.ts @@ -286,8 +286,6 @@ export class SchemaService { return 0; } - - cleanArray(values: any[], itemSchema: SomeJSONSchema) { if (Array.isArray(values)) { switch (itemSchema.type) { diff --git a/src/app/utils/params.ts b/src/app/utils/params.ts index 993ae30..e943eb0 100644 --- a/src/app/utils/params.ts +++ b/src/app/utils/params.ts @@ -1,6 +1,13 @@ -import { FormGroup, ValidatorFn, Validators, FormControl, AbstractControl, ValidationErrors } from "@angular/forms"; -import { SomeJSONSchema } from "ajv/dist/types/json-schema"; -import { ParamsFormInfo } from "../models/form"; +import { + FormGroup, + ValidatorFn, + Validators, + FormControl, + AbstractControl, + ValidationErrors, +} from '@angular/forms'; +import { SomeJSONSchema } from 'ajv/dist/types/json-schema'; +import { ParamsFormInfo } from '../models/form'; export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo { const paramsFormInfo: ParamsFormInfo = { @@ -67,10 +74,7 @@ export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo { } //TODO(jwetzell): figure out how to disable a control but not have to deal with undefined values on disabled controls - paramsFormInfo.formGroup.addControl( - paramKey, - new FormControl(formDefault, validators), - ); + paramsFormInfo.formGroup.addControl(paramKey, new FormControl(formDefault, validators)); if (paramSchema.enum) { paramsFormInfo.paramsInfo[paramKey].options = paramSchema.enum; @@ -100,11 +104,7 @@ export function cleanParams(paramsSchema: SomeJSONSchema, params: any): any { const paramSchema = paramsSchema.properties[paramKey]; // delete null/undefined/empty params that aren't required - if ( - params[paramKey] === undefined || - params[paramKey] === null || - params[paramKey] === '' - ) { + if (params[paramKey] === undefined || params[paramKey] === null || params[paramKey] === '') { if (paramSchema.required) { if (!paramSchema.includes(paramKey)) { delete params[paramKey]; @@ -203,4 +203,4 @@ export function parseStringToArray(value: any, schema: SomeJSONSchema): any[] | } } return undefined; -} \ No newline at end of file +}