From 6b6928027788cd7ca4934ee18b9b738fd5e528b6 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 14:20:18 +0100 Subject: [PATCH 1/8] Centralized HTTPClient and status checks --- backend/aethel_db/views/status.py | 2 +- backend/parseport/http_client.py | 4 ++++ backend/parseport/settings.py | 2 ++ backend/parseport/views.py | 36 ++++++++++++++++++++++++++----- backend/spindle/views.py | 21 +++--------------- 5 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 backend/parseport/http_client.py diff --git a/backend/aethel_db/views/status.py b/backend/aethel_db/views/status.py index 13bd81b..3f06d43 100644 --- a/backend/aethel_db/views/status.py +++ b/backend/aethel_db/views/status.py @@ -1,5 +1,5 @@ from aethel_db.models import dataset -def aethel_status(): +def aethel_status() -> bool: return dataset is not None diff --git a/backend/parseport/http_client.py b/backend/parseport/http_client.py new file mode 100644 index 0000000..ac6ff76 --- /dev/null +++ b/backend/parseport/http_client.py @@ -0,0 +1,4 @@ +import urllib3 + + +http_client = urllib3.PoolManager() diff --git a/backend/parseport/settings.py b/backend/parseport/settings.py index ca41151..951fc6b 100644 --- a/backend/parseport/settings.py +++ b/backend/parseport/settings.py @@ -123,6 +123,8 @@ SPINDLE_URL = f"http://pp-spindle:32768/" LATEX_SERVICE_URL = f"http://pp-latex:32769/" +MINIMALIST_PARSER_URL = f"http://pp-minimalist-parser:32770/" +VULCAN_URL = f"http://pp-vulcan:32771/" data_subset_path = "./aethel_db/data/aethel_subset.pickle" full_dataset_path = "/data/aethel.pickle" diff --git a/backend/parseport/views.py b/backend/parseport/views.py index 170c6b7..758fc42 100644 --- a/backend/parseport/views.py +++ b/backend/parseport/views.py @@ -1,13 +1,39 @@ +from typing import Literal +from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response +from parseport.http_client import http_client from aethel_db.views.status import aethel_status -from spindle.views import spindle_status + +SERVICE_URL_MAPPING = { + "spindle": settings.SPINDLE_URL, + "minimalist_parser": settings.MINIMALIST_PARSER_URL, + "vulcan": settings.VULCAN_URL, +} + + +def status_check(service: Literal["spindle", "minimalist_parser", "vulcan"]) -> bool: + try: + r = http_client.request( + method="GET", + url=SERVICE_URL_MAPPING[service] + "/status/", + headers={"Content-Type": "application/json"}, + timeout=1, + retries=False, + ) + return r.status < 400 + except Exception: + return False class StatusView(APIView): def get(self, request): - return Response(dict( - aethel=aethel_status(), - spindle=spindle_status() - )) + return Response( + dict( + aethel=aethel_status(), + spindle=status_check('spindle'), + mp=status_check('minimalist_parser'), + vulcan=status_check('vulcan'), + ) + ) diff --git a/backend/spindle/views.py b/backend/spindle/views.py index 1922f88..9a496fc 100644 --- a/backend/spindle/views.py +++ b/backend/spindle/views.py @@ -23,8 +23,7 @@ ) from spindle.utils import serialize_phrases - -http = urllib3.PoolManager() +from parseport.http_client import http_client # Output mode @@ -105,7 +104,7 @@ def post(self, request: HttpRequest, mode: Mode): def send_to_parser(self, text: str) -> Optional[ParserResponse]: """Send request to downstream (natural language) parser""" # Sending data to Spindle container. - spindle_response = http.request( + spindle_response = http_client.request( method="POST", url=settings.SPINDLE_URL, body=json.dumps({"input": text}), @@ -163,7 +162,7 @@ def latex_response(self, latex: str) -> JsonResponse: def pdf_response(self, latex: str) -> JsonResponse: """Forward LaTeX code to LaTeX service. Return PDF""" - latex_response = http.request( + latex_response = http_client.request( method="POST", url=settings.LATEX_SERVICE_URL, body=latex, @@ -208,17 +207,3 @@ def proof_response(self, parsed: ParserResponse) -> JsonResponse: return SpindleResponse( proof=serial_proof_to_json(serialize_proof(parsed.proof)) ).json_response() - - -def spindle_status(): - try: - r = http.request( - method="GET", - url=settings.SPINDLE_URL + "/status/", - headers={"Content-Type": "application/json"}, - timeout=1, - retries=False, - ) - return r.status < 400 - except Exception: - return False From 0927ca6a34e7f51415fd8732c82473872f795c93 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 15:11:22 +0100 Subject: [PATCH 2/8] Added minimalist_parser backend app --- backend/minimalist_parser/__init__.py | 0 backend/minimalist_parser/admin.py | 3 +++ backend/minimalist_parser/apps.py | 6 ++++++ backend/minimalist_parser/migrations/__init__.py | 0 backend/minimalist_parser/models.py | 3 +++ backend/minimalist_parser/tests.py | 3 +++ backend/minimalist_parser/urls.py | 5 +++++ backend/minimalist_parser/views/parse.py | 8 ++++++++ backend/minimalist_parser/views/visualise.py | 0 backend/parseport/settings.py | 1 + backend/parseport/urls.py | 2 ++ 11 files changed, 31 insertions(+) create mode 100644 backend/minimalist_parser/__init__.py create mode 100644 backend/minimalist_parser/admin.py create mode 100644 backend/minimalist_parser/apps.py create mode 100644 backend/minimalist_parser/migrations/__init__.py create mode 100644 backend/minimalist_parser/models.py create mode 100644 backend/minimalist_parser/tests.py create mode 100644 backend/minimalist_parser/urls.py create mode 100644 backend/minimalist_parser/views/parse.py create mode 100644 backend/minimalist_parser/views/visualise.py diff --git a/backend/minimalist_parser/__init__.py b/backend/minimalist_parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/minimalist_parser/admin.py b/backend/minimalist_parser/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/minimalist_parser/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/minimalist_parser/apps.py b/backend/minimalist_parser/apps.py new file mode 100644 index 0000000..b3c3aeb --- /dev/null +++ b/backend/minimalist_parser/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MinimalistParserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'minimalist_parser' diff --git a/backend/minimalist_parser/migrations/__init__.py b/backend/minimalist_parser/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/minimalist_parser/models.py b/backend/minimalist_parser/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/backend/minimalist_parser/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/backend/minimalist_parser/tests.py b/backend/minimalist_parser/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/minimalist_parser/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/minimalist_parser/urls.py b/backend/minimalist_parser/urls.py new file mode 100644 index 0000000..6d0a063 --- /dev/null +++ b/backend/minimalist_parser/urls.py @@ -0,0 +1,5 @@ +from django.urls import path + +from minimalist_parser.views.parse import MPParseView + +urlpatterns = [path("parse", MPParseView.as_view(), name="mp-parse")] diff --git a/backend/minimalist_parser/views/parse.py b/backend/minimalist_parser/views/parse.py new file mode 100644 index 0000000..3433020 --- /dev/null +++ b/backend/minimalist_parser/views/parse.py @@ -0,0 +1,8 @@ +from django.http import HttpRequest, HttpResponse, JsonResponse +from django.views import View + + +# Create your views here. +class MPParseView(View): + def post(self, request: HttpRequest) -> HttpResponse: + return JsonResponse({"message": "MP Parser is connected!"}) diff --git a/backend/minimalist_parser/views/visualise.py b/backend/minimalist_parser/views/visualise.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/parseport/settings.py b/backend/parseport/settings.py index 951fc6b..120e33e 100644 --- a/backend/parseport/settings.py +++ b/backend/parseport/settings.py @@ -37,6 +37,7 @@ "revproxy", "corsheaders", "aethel_db", + "minimalist_parser", ] MIDDLEWARE = [ diff --git a/backend/parseport/urls.py b/backend/parseport/urls.py index 51d6cea..66d3d62 100644 --- a/backend/parseport/urls.py +++ b/backend/parseport/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.conf.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.urls import path, include from django.contrib import admin from django.views.generic import RedirectView @@ -35,6 +36,7 @@ path("api/status/", StatusView.as_view(), name="status"), path("api/spindle/", SpindleView.as_view(), name="spindle"), path("api/aethel/", include("aethel_db.urls")), + path("api/mp/", include("minimalist_parser.urls")), path( "api-auth/", include( From d8864405165f95d8a04bb993dca4899e9ec879b2 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 15:38:58 +0100 Subject: [PATCH 3/8] MP parser input component and status checks --- backend/parseport/views.py | 7 +- frontend/src/app/aethel/aethel.component.html | 2 +- frontend/src/app/aethel/aethel.component.ts | 11 ++- frontend/src/app/app.module.ts | 6 ++ .../minimalist-parser-input.component.html | 44 +++++++++--- .../minimalist-parser-input.component.scss | 10 +++ .../minimalist-parser-input.component.ts | 48 ++++++++++++- .../minimalist-parser.module.ts | 4 +- .../shared/services/mp-api.service.spec.ts | 16 +++++ .../src/app/shared/services/mp-api.service.ts | 69 +++++++++++++++++++ .../src/app/shared/services/status.service.ts | 8 ++- frontend/src/app/shared/shared.module.ts | 8 ++- frontend/src/app/spindle/spindle.component.ts | 2 +- 13 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 frontend/src/app/shared/services/mp-api.service.spec.ts create mode 100644 frontend/src/app/shared/services/mp-api.service.ts diff --git a/backend/parseport/views.py b/backend/parseport/views.py index 758fc42..6b93395 100644 --- a/backend/parseport/views.py +++ b/backend/parseport/views.py @@ -33,7 +33,10 @@ def get(self, request): dict( aethel=aethel_status(), spindle=status_check('spindle'), - mp=status_check('minimalist_parser'), - vulcan=status_check('vulcan'), + mp=True, + vulcan=True, + # When the minimalist_parser and vulcan services are up and running, uncomment the following lines. + # mp=status_check('minimalist_parser'), + # vulcan=status_check('vulcan'), ) ) diff --git a/frontend/src/app/aethel/aethel.component.html b/frontend/src/app/aethel/aethel.component.html index bae67ae..dbd30ef 100644 --- a/frontend/src/app/aethel/aethel.component.html +++ b/frontend/src/app/aethel/aethel.component.html @@ -5,7 +5,7 @@

Æthel

start.

-@if (status$ | async) { +@if (statusOk$ | async) {
diff --git a/frontend/src/app/aethel/aethel.component.ts b/frontend/src/app/aethel/aethel.component.ts index 06f3c58..815f86e 100644 --- a/frontend/src/app/aethel/aethel.component.ts +++ b/frontend/src/app/aethel/aethel.component.ts @@ -3,7 +3,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { AethelInput, AethelListResult } from "../shared/types"; import { AethelApiService } from "../shared/services/aethel-api.service"; -import { Subject, distinctUntilChanged, map } from "rxjs"; +import { distinctUntilChanged, map } from "rxjs"; import { faChevronDown, faChevronRight, @@ -44,7 +44,9 @@ export class AethelComponent implements OnInit { chevronDown: faChevronDown, }; - public status$ = new Subject(); + public statusOk$ = this.statusService.getStatus$().pipe( + map((status) => status.aethel), + ); constructor( private apiService: AethelApiService, @@ -55,11 +57,6 @@ export class AethelComponent implements OnInit { ) {} ngOnInit(): void { - this.statusService - .get() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((status) => this.status$.next(status.aethel)); - this.apiService.output$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((response) => { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fd3f50a..09b41f0 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -21,6 +21,7 @@ import { SampleComponent } from "./sample/sample.component"; import { SampleDataComponent } from "./aethel/sample-details/sample-data.component"; import { AboutComponent } from "./about/about.component"; import { SharedModule } from "./shared/shared.module"; +import { MinimalistParserModule } from "./minimalist-parser/minimalist-parser.module"; @NgModule({ declarations: [ @@ -46,6 +47,11 @@ import { SharedModule } from "./shared/shared.module"; FontAwesomeModule, TableModule, SharedModule, + MinimalistParserModule + ], + exports: [ + FontAwesomeModule, + SharedModule, ], bootstrap: [AppComponent], }) diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html index a7efbd2..8f5683a 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html @@ -2,15 +2,43 @@

Minimalist Parser

Please enter a sentence below to begin.

+@if (statusOk$ | async) {
- -
- -
+ + +
+
+ +
+ +
+ @if (form.touched && form.invalid) { +

+ Please enter a sentence first. +

+ } +
- - +} @else { +

+ The Minimalist Parser is temporarily unavailable. +

+} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss index e69de29..d00a1f8 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss @@ -0,0 +1,10 @@ +.control-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.control { + width: 100%; +} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts index 10698ce..4bda0e9 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts @@ -1,8 +1,52 @@ -import { Component } from "@angular/core"; +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { map } from "rxjs"; +import { MpApiService } from "src/app/shared/services/mp-api.service"; +import { StatusService } from "src/app/shared/services/status.service"; @Component({ selector: "pp-minimalist-parser-input", templateUrl: "./minimalist-parser-input.component.html", styleUrl: "./minimalist-parser-input.component.scss", }) -export class MinimalistParserInputComponent {} +export class MinimalistParserInputComponent implements OnInit { + public form = new FormGroup({ + mpInput: new FormControl("", { + validators: [Validators.required], + }), + }); + + public loading$ = this.apiService.loading$; + + public statusOk$ = this.statusService + .getStatus$() + .pipe(map((status) => status.mp && status.vulcan)); + + constructor( + private destroyRef: DestroyRef, + private apiService: MpApiService, + private statusService: StatusService, + ) {} + + ngOnInit(): void { + this.apiService.output$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((response) => { + if (!response) { + return; + } + // Do something with the response. + }); + } + + public parse(): void { + this.form.controls.mpInput.markAsTouched(); + this.form.controls.mpInput.updateValueAndValidity(); + const input = this.form.controls.mpInput.value; + if (this.form.invalid || !input) { + return; + } + this.apiService.input$.next(input); + } +} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser.module.ts b/frontend/src/app/minimalist-parser/minimalist-parser.module.ts index 1010862..c4a585e 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser.module.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser.module.ts @@ -1,10 +1,10 @@ import { NgModule } from "@angular/core"; import { MinimalistParserComponent } from "./minimalist-parser.component"; -import { CommonModule } from "@angular/common"; import { MinimalistParserAboutComponent } from "./minimalist-parser-about/minimalist-parser-about.component"; import { MinimalistParserInputComponent } from "./minimalist-parser-input/minimalist-parser-input.component"; import { MinimalistParserReferencesComponent } from "./minimalist-parser-references/minimalist-parser-references.component"; import { MinimalistParserBrowserComponent } from "./minimalist-parser-browser/minimalist-parser-browser.component"; +import { SharedModule } from "../shared/shared.module"; @NgModule({ declarations: [ @@ -14,6 +14,6 @@ import { MinimalistParserBrowserComponent } from "./minimalist-parser-browser/mi MinimalistParserReferencesComponent, MinimalistParserBrowserComponent, ], - imports: [CommonModule], + imports: [SharedModule], }) export class MinimalistParserModule {} diff --git a/frontend/src/app/shared/services/mp-api.service.spec.ts b/frontend/src/app/shared/services/mp-api.service.spec.ts new file mode 100644 index 0000000..e525e6a --- /dev/null +++ b/frontend/src/app/shared/services/mp-api.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from "@angular/core/testing"; + +import { MpApiService } from "./mp-api.service"; + +describe("MpApiService", () => { + let service: MpApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MpApiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/shared/services/mp-api.service.ts b/frontend/src/app/shared/services/mp-api.service.ts new file mode 100644 index 0000000..e7d1610 --- /dev/null +++ b/frontend/src/app/shared/services/mp-api.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from "@angular/core"; +import { ParsePortDataService } from "./ParsePortDataService"; +import { + catchError, + distinctUntilChanged, + map, + merge, + of, + share, + Subject, + switchMap, + throttleTime, +} from "rxjs"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { environment } from "src/environments/environment"; +import { ErrorHandlerService } from "./error-handler.service"; + +type MPInput = string; +type MPOutput = string; +type MPLoading = boolean; + +@Injectable({ + providedIn: "root", +}) +export class MpApiService + implements ParsePortDataService +{ + public input$ = new Subject(); + + public throttledInput$ = this.input$.pipe( + distinctUntilChanged(), + throttleTime(300), + ); + + public output$ = this.throttledInput$.pipe( + switchMap((input) => + this.http + .post( + `${environment.apiUrl}mp/parse`, + { input }, + { + headers: new HttpHeaders({ + "Content-Type": "application/json", + }), + }, + ) + .pipe( + catchError((error) => { + this.errorHandler.handleHttpError( + error, + $localize`An error occurred while handling your input.`, + ); + return of(null); + }), + ), + ), + share(), + ); + + public loading$ = merge( + this.throttledInput$.pipe(map(() => true)), + this.output$.pipe(map(() => false)), + ); + + constructor( + private http: HttpClient, + private errorHandler: ErrorHandlerService, + ) {} +} diff --git a/frontend/src/app/shared/services/status.service.ts b/frontend/src/app/shared/services/status.service.ts index a1164c5..e397837 100644 --- a/frontend/src/app/shared/services/status.service.ts +++ b/frontend/src/app/shared/services/status.service.ts @@ -6,15 +6,17 @@ import { environment } from "src/environments/environment"; interface Status { aethel: boolean; spindle: boolean; + mp: boolean; + vulcan: boolean; } @Injectable({ - providedIn: "root" + providedIn: "root", }) -export class StatusService{ +export class StatusService { constructor(private http: HttpClient) {} - get(): Observable { + public getStatus$(): Observable { return this.http.get(`${environment.apiUrl}status/`); } } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 3e8dffc..05d1a8b 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -14,6 +14,8 @@ import { CommonModule } from "@angular/common"; import { ExportTextComponent } from "./components/spindle-export/export-text/export-text.component"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { SpindleExportComponent } from "./components/spindle-export/spindle-export.component"; +import { MpApiService } from "./services/mp-api.service"; +import { ReactiveFormsModule } from "@angular/forms"; @NgModule({ declarations: [ @@ -25,7 +27,7 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ExportTextComponent, SpindleExportComponent, ], - imports: [CommonModule, FontAwesomeModule], + imports: [CommonModule, FontAwesomeModule, ReactiveFormsModule], providers: [ AethelApiService, AlertService, @@ -33,6 +35,7 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ErrorHandlerService, SpindleApiService, StatusService, + MpApiService, ], exports: [ AlertComponent, @@ -40,6 +43,9 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ProofPipe, SpindleExportComponent, ExportButtonComponent, + CommonModule, + ReactiveFormsModule, + FontAwesomeModule, ], }) export class SharedModule {} diff --git a/frontend/src/app/spindle/spindle.component.ts b/frontend/src/app/spindle/spindle.component.ts index 00dc4ab..6854acc 100644 --- a/frontend/src/app/spindle/spindle.component.ts +++ b/frontend/src/app/spindle/spindle.component.ts @@ -26,7 +26,7 @@ export class SpindleComponent implements OnInit { spindleReady$ = timer(0, 5000).pipe( takeUntil(this.stopStatus$), - switchMap(() => this.statusService.get()), + switchMap(() => this.statusService.getStatus$()), map((status) => status.spindle), share(), ); From 7db4393f23657cfcb4a1472a705aa1acc3f71782 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 16:11:40 +0100 Subject: [PATCH 4/8] Fix failing tests --- .../minimalist-parser-input.component.spec.ts | 7 ++++++- frontend/src/app/shared/services/mp-api.service.spec.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts index 6203f57..7aba97e 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts @@ -1,13 +1,18 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MinimalistParserInputComponent } from "./minimalist-parser-input.component"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { SharedModule } from "src/app/shared/shared.module"; describe("MinimalistParserInputComponent", () => { let component: MinimalistParserInputComponent; let fixture: ComponentFixture; beforeEach(async () => { - await TestBed.configureTestingModule({}).compileComponents(); + await TestBed.configureTestingModule({ + declarations: [MinimalistParserInputComponent], + imports: [HttpClientTestingModule, SharedModule], + }).compileComponents(); fixture = TestBed.createComponent(MinimalistParserInputComponent); component = fixture.componentInstance; diff --git a/frontend/src/app/shared/services/mp-api.service.spec.ts b/frontend/src/app/shared/services/mp-api.service.spec.ts index e525e6a..708d43b 100644 --- a/frontend/src/app/shared/services/mp-api.service.spec.ts +++ b/frontend/src/app/shared/services/mp-api.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from "@angular/core/testing"; import { MpApiService } from "./mp-api.service"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; describe("MpApiService", () => { let service: MpApiService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); service = TestBed.inject(MpApiService); }); From 5c73086aa044e5ae4ea86424ae5019fdc9b5c850 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 14:20:18 +0100 Subject: [PATCH 5/8] Centralized HTTPClient and status checks --- backend/aethel_db/views/status.py | 2 +- backend/parseport/http_client.py | 4 ++++ backend/parseport/settings.py | 2 ++ backend/parseport/views.py | 36 ++++++++++++++++++++++++++----- backend/spindle/views.py | 21 +++--------------- 5 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 backend/parseport/http_client.py diff --git a/backend/aethel_db/views/status.py b/backend/aethel_db/views/status.py index 13bd81b..3f06d43 100644 --- a/backend/aethel_db/views/status.py +++ b/backend/aethel_db/views/status.py @@ -1,5 +1,5 @@ from aethel_db.models import dataset -def aethel_status(): +def aethel_status() -> bool: return dataset is not None diff --git a/backend/parseport/http_client.py b/backend/parseport/http_client.py new file mode 100644 index 0000000..ac6ff76 --- /dev/null +++ b/backend/parseport/http_client.py @@ -0,0 +1,4 @@ +import urllib3 + + +http_client = urllib3.PoolManager() diff --git a/backend/parseport/settings.py b/backend/parseport/settings.py index ca41151..951fc6b 100644 --- a/backend/parseport/settings.py +++ b/backend/parseport/settings.py @@ -123,6 +123,8 @@ SPINDLE_URL = f"http://pp-spindle:32768/" LATEX_SERVICE_URL = f"http://pp-latex:32769/" +MINIMALIST_PARSER_URL = f"http://pp-minimalist-parser:32770/" +VULCAN_URL = f"http://pp-vulcan:32771/" data_subset_path = "./aethel_db/data/aethel_subset.pickle" full_dataset_path = "/data/aethel.pickle" diff --git a/backend/parseport/views.py b/backend/parseport/views.py index 170c6b7..758fc42 100644 --- a/backend/parseport/views.py +++ b/backend/parseport/views.py @@ -1,13 +1,39 @@ +from typing import Literal +from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response +from parseport.http_client import http_client from aethel_db.views.status import aethel_status -from spindle.views import spindle_status + +SERVICE_URL_MAPPING = { + "spindle": settings.SPINDLE_URL, + "minimalist_parser": settings.MINIMALIST_PARSER_URL, + "vulcan": settings.VULCAN_URL, +} + + +def status_check(service: Literal["spindle", "minimalist_parser", "vulcan"]) -> bool: + try: + r = http_client.request( + method="GET", + url=SERVICE_URL_MAPPING[service] + "/status/", + headers={"Content-Type": "application/json"}, + timeout=1, + retries=False, + ) + return r.status < 400 + except Exception: + return False class StatusView(APIView): def get(self, request): - return Response(dict( - aethel=aethel_status(), - spindle=spindle_status() - )) + return Response( + dict( + aethel=aethel_status(), + spindle=status_check('spindle'), + mp=status_check('minimalist_parser'), + vulcan=status_check('vulcan'), + ) + ) diff --git a/backend/spindle/views.py b/backend/spindle/views.py index 1922f88..9a496fc 100644 --- a/backend/spindle/views.py +++ b/backend/spindle/views.py @@ -23,8 +23,7 @@ ) from spindle.utils import serialize_phrases - -http = urllib3.PoolManager() +from parseport.http_client import http_client # Output mode @@ -105,7 +104,7 @@ def post(self, request: HttpRequest, mode: Mode): def send_to_parser(self, text: str) -> Optional[ParserResponse]: """Send request to downstream (natural language) parser""" # Sending data to Spindle container. - spindle_response = http.request( + spindle_response = http_client.request( method="POST", url=settings.SPINDLE_URL, body=json.dumps({"input": text}), @@ -163,7 +162,7 @@ def latex_response(self, latex: str) -> JsonResponse: def pdf_response(self, latex: str) -> JsonResponse: """Forward LaTeX code to LaTeX service. Return PDF""" - latex_response = http.request( + latex_response = http_client.request( method="POST", url=settings.LATEX_SERVICE_URL, body=latex, @@ -208,17 +207,3 @@ def proof_response(self, parsed: ParserResponse) -> JsonResponse: return SpindleResponse( proof=serial_proof_to_json(serialize_proof(parsed.proof)) ).json_response() - - -def spindle_status(): - try: - r = http.request( - method="GET", - url=settings.SPINDLE_URL + "/status/", - headers={"Content-Type": "application/json"}, - timeout=1, - retries=False, - ) - return r.status < 400 - except Exception: - return False From 6db8503516d2d3eaa35bd8a2f0d780f759926e62 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 15:11:22 +0100 Subject: [PATCH 6/8] Added minimalist_parser backend app --- backend/minimalist_parser/__init__.py | 0 backend/minimalist_parser/admin.py | 3 +++ backend/minimalist_parser/apps.py | 6 ++++++ backend/minimalist_parser/migrations/__init__.py | 0 backend/minimalist_parser/models.py | 3 +++ backend/minimalist_parser/tests.py | 3 +++ backend/minimalist_parser/urls.py | 5 +++++ backend/minimalist_parser/views/parse.py | 8 ++++++++ backend/minimalist_parser/views/visualise.py | 0 backend/parseport/settings.py | 1 + backend/parseport/urls.py | 2 ++ 11 files changed, 31 insertions(+) create mode 100644 backend/minimalist_parser/__init__.py create mode 100644 backend/minimalist_parser/admin.py create mode 100644 backend/minimalist_parser/apps.py create mode 100644 backend/minimalist_parser/migrations/__init__.py create mode 100644 backend/minimalist_parser/models.py create mode 100644 backend/minimalist_parser/tests.py create mode 100644 backend/minimalist_parser/urls.py create mode 100644 backend/minimalist_parser/views/parse.py create mode 100644 backend/minimalist_parser/views/visualise.py diff --git a/backend/minimalist_parser/__init__.py b/backend/minimalist_parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/minimalist_parser/admin.py b/backend/minimalist_parser/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/minimalist_parser/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/minimalist_parser/apps.py b/backend/minimalist_parser/apps.py new file mode 100644 index 0000000..b3c3aeb --- /dev/null +++ b/backend/minimalist_parser/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MinimalistParserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'minimalist_parser' diff --git a/backend/minimalist_parser/migrations/__init__.py b/backend/minimalist_parser/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/minimalist_parser/models.py b/backend/minimalist_parser/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/backend/minimalist_parser/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/backend/minimalist_parser/tests.py b/backend/minimalist_parser/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/minimalist_parser/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/minimalist_parser/urls.py b/backend/minimalist_parser/urls.py new file mode 100644 index 0000000..6d0a063 --- /dev/null +++ b/backend/minimalist_parser/urls.py @@ -0,0 +1,5 @@ +from django.urls import path + +from minimalist_parser.views.parse import MPParseView + +urlpatterns = [path("parse", MPParseView.as_view(), name="mp-parse")] diff --git a/backend/minimalist_parser/views/parse.py b/backend/minimalist_parser/views/parse.py new file mode 100644 index 0000000..3433020 --- /dev/null +++ b/backend/minimalist_parser/views/parse.py @@ -0,0 +1,8 @@ +from django.http import HttpRequest, HttpResponse, JsonResponse +from django.views import View + + +# Create your views here. +class MPParseView(View): + def post(self, request: HttpRequest) -> HttpResponse: + return JsonResponse({"message": "MP Parser is connected!"}) diff --git a/backend/minimalist_parser/views/visualise.py b/backend/minimalist_parser/views/visualise.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/parseport/settings.py b/backend/parseport/settings.py index 951fc6b..120e33e 100644 --- a/backend/parseport/settings.py +++ b/backend/parseport/settings.py @@ -37,6 +37,7 @@ "revproxy", "corsheaders", "aethel_db", + "minimalist_parser", ] MIDDLEWARE = [ diff --git a/backend/parseport/urls.py b/backend/parseport/urls.py index 51d6cea..66d3d62 100644 --- a/backend/parseport/urls.py +++ b/backend/parseport/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.conf.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.urls import path, include from django.contrib import admin from django.views.generic import RedirectView @@ -35,6 +36,7 @@ path("api/status/", StatusView.as_view(), name="status"), path("api/spindle/", SpindleView.as_view(), name="spindle"), path("api/aethel/", include("aethel_db.urls")), + path("api/mp/", include("minimalist_parser.urls")), path( "api-auth/", include( From 04bc76689d1f0d2e7ff25c52b7fe2af40f76d733 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 15:38:58 +0100 Subject: [PATCH 7/8] MP parser input component and status checks --- backend/parseport/views.py | 7 +- frontend/src/app/aethel/aethel.component.html | 39 +++++------ frontend/src/app/aethel/aethel.component.ts | 11 ++- frontend/src/app/app.module.ts | 6 ++ .../minimalist-parser-input.component.html | 44 +++++++++--- .../minimalist-parser-input.component.scss | 10 +++ .../minimalist-parser-input.component.ts | 48 ++++++++++++- .../minimalist-parser.module.ts | 4 +- .../shared/services/mp-api.service.spec.ts | 16 +++++ .../src/app/shared/services/mp-api.service.ts | 69 +++++++++++++++++++ .../src/app/shared/services/status.service.ts | 8 ++- frontend/src/app/shared/shared.module.ts | 8 ++- frontend/src/app/spindle/spindle.component.ts | 2 +- 13 files changed, 226 insertions(+), 46 deletions(-) create mode 100644 frontend/src/app/shared/services/mp-api.service.spec.ts create mode 100644 frontend/src/app/shared/services/mp-api.service.ts diff --git a/backend/parseport/views.py b/backend/parseport/views.py index 758fc42..6b93395 100644 --- a/backend/parseport/views.py +++ b/backend/parseport/views.py @@ -33,7 +33,10 @@ def get(self, request): dict( aethel=aethel_status(), spindle=status_check('spindle'), - mp=status_check('minimalist_parser'), - vulcan=status_check('vulcan'), + mp=True, + vulcan=True, + # When the minimalist_parser and vulcan services are up and running, uncomment the following lines. + # mp=status_check('minimalist_parser'), + # vulcan=status_check('vulcan'), ) ) diff --git a/frontend/src/app/aethel/aethel.component.html b/frontend/src/app/aethel/aethel.component.html index 45f7903..2fa9a3f 100644 --- a/frontend/src/app/aethel/aethel.component.html +++ b/frontend/src/app/aethel/aethel.component.html @@ -12,27 +12,9 @@

Æthel

syntactic analyses of LASSY Small, the gold standard corpus of written Dutch.

-

- More info can be found under - About and - References. The - notations are explained under - Notation. -

-

- You can use the interface below to search for a word or lemma. - Once you have retrieved a sample, you can inspect it to isolate - and look into a word, a type, or a word-type pair. You can then - look for other words that occur with the same type, or other - occurrences of the same word-type pair. -

- @if (status$ | async) { -
+ @if (statusOk$ | async) { +
@@ -57,6 +39,23 @@

Æthel

Please enter at least three characters.

+

+ More info can be found under + About and + References. + The notations are explained under + Notation. +

+

+ You can use the interface below to search for a word or + lemma. Once you have retrieved a sample, you can inspect + it to isolate and look into a word, a type, or a + word-type pair. You can then look for other words that + occur with the same type, or other occurrences of the + same word-type pair. +

}
diff --git a/frontend/src/app/aethel/aethel.component.ts b/frontend/src/app/aethel/aethel.component.ts index 06f3c58..815f86e 100644 --- a/frontend/src/app/aethel/aethel.component.ts +++ b/frontend/src/app/aethel/aethel.component.ts @@ -3,7 +3,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { AethelInput, AethelListResult } from "../shared/types"; import { AethelApiService } from "../shared/services/aethel-api.service"; -import { Subject, distinctUntilChanged, map } from "rxjs"; +import { distinctUntilChanged, map } from "rxjs"; import { faChevronDown, faChevronRight, @@ -44,7 +44,9 @@ export class AethelComponent implements OnInit { chevronDown: faChevronDown, }; - public status$ = new Subject(); + public statusOk$ = this.statusService.getStatus$().pipe( + map((status) => status.aethel), + ); constructor( private apiService: AethelApiService, @@ -55,11 +57,6 @@ export class AethelComponent implements OnInit { ) {} ngOnInit(): void { - this.statusService - .get() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((status) => this.status$.next(status.aethel)); - this.apiService.output$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((response) => { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fd3f50a..09b41f0 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -21,6 +21,7 @@ import { SampleComponent } from "./sample/sample.component"; import { SampleDataComponent } from "./aethel/sample-details/sample-data.component"; import { AboutComponent } from "./about/about.component"; import { SharedModule } from "./shared/shared.module"; +import { MinimalistParserModule } from "./minimalist-parser/minimalist-parser.module"; @NgModule({ declarations: [ @@ -46,6 +47,11 @@ import { SharedModule } from "./shared/shared.module"; FontAwesomeModule, TableModule, SharedModule, + MinimalistParserModule + ], + exports: [ + FontAwesomeModule, + SharedModule, ], bootstrap: [AppComponent], }) diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html index a7efbd2..8f5683a 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.html @@ -2,15 +2,43 @@

Minimalist Parser

Please enter a sentence below to begin.

+@if (statusOk$ | async) {
- -
- -
+
+ +
+
+ +
+ +
+ @if (form.touched && form.invalid) { +

+ Please enter a sentence first. +

+ } +
- - +} @else { +

+ The Minimalist Parser is temporarily unavailable. +

+} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss index e69de29..d00a1f8 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.scss @@ -0,0 +1,10 @@ +.control-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.control { + width: 100%; +} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts index 10698ce..4bda0e9 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.ts @@ -1,8 +1,52 @@ -import { Component } from "@angular/core"; +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { map } from "rxjs"; +import { MpApiService } from "src/app/shared/services/mp-api.service"; +import { StatusService } from "src/app/shared/services/status.service"; @Component({ selector: "pp-minimalist-parser-input", templateUrl: "./minimalist-parser-input.component.html", styleUrl: "./minimalist-parser-input.component.scss", }) -export class MinimalistParserInputComponent {} +export class MinimalistParserInputComponent implements OnInit { + public form = new FormGroup({ + mpInput: new FormControl("", { + validators: [Validators.required], + }), + }); + + public loading$ = this.apiService.loading$; + + public statusOk$ = this.statusService + .getStatus$() + .pipe(map((status) => status.mp && status.vulcan)); + + constructor( + private destroyRef: DestroyRef, + private apiService: MpApiService, + private statusService: StatusService, + ) {} + + ngOnInit(): void { + this.apiService.output$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((response) => { + if (!response) { + return; + } + // Do something with the response. + }); + } + + public parse(): void { + this.form.controls.mpInput.markAsTouched(); + this.form.controls.mpInput.updateValueAndValidity(); + const input = this.form.controls.mpInput.value; + if (this.form.invalid || !input) { + return; + } + this.apiService.input$.next(input); + } +} diff --git a/frontend/src/app/minimalist-parser/minimalist-parser.module.ts b/frontend/src/app/minimalist-parser/minimalist-parser.module.ts index 1010862..c4a585e 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser.module.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser.module.ts @@ -1,10 +1,10 @@ import { NgModule } from "@angular/core"; import { MinimalistParserComponent } from "./minimalist-parser.component"; -import { CommonModule } from "@angular/common"; import { MinimalistParserAboutComponent } from "./minimalist-parser-about/minimalist-parser-about.component"; import { MinimalistParserInputComponent } from "./minimalist-parser-input/minimalist-parser-input.component"; import { MinimalistParserReferencesComponent } from "./minimalist-parser-references/minimalist-parser-references.component"; import { MinimalistParserBrowserComponent } from "./minimalist-parser-browser/minimalist-parser-browser.component"; +import { SharedModule } from "../shared/shared.module"; @NgModule({ declarations: [ @@ -14,6 +14,6 @@ import { MinimalistParserBrowserComponent } from "./minimalist-parser-browser/mi MinimalistParserReferencesComponent, MinimalistParserBrowserComponent, ], - imports: [CommonModule], + imports: [SharedModule], }) export class MinimalistParserModule {} diff --git a/frontend/src/app/shared/services/mp-api.service.spec.ts b/frontend/src/app/shared/services/mp-api.service.spec.ts new file mode 100644 index 0000000..e525e6a --- /dev/null +++ b/frontend/src/app/shared/services/mp-api.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from "@angular/core/testing"; + +import { MpApiService } from "./mp-api.service"; + +describe("MpApiService", () => { + let service: MpApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MpApiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/shared/services/mp-api.service.ts b/frontend/src/app/shared/services/mp-api.service.ts new file mode 100644 index 0000000..e7d1610 --- /dev/null +++ b/frontend/src/app/shared/services/mp-api.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from "@angular/core"; +import { ParsePortDataService } from "./ParsePortDataService"; +import { + catchError, + distinctUntilChanged, + map, + merge, + of, + share, + Subject, + switchMap, + throttleTime, +} from "rxjs"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { environment } from "src/environments/environment"; +import { ErrorHandlerService } from "./error-handler.service"; + +type MPInput = string; +type MPOutput = string; +type MPLoading = boolean; + +@Injectable({ + providedIn: "root", +}) +export class MpApiService + implements ParsePortDataService +{ + public input$ = new Subject(); + + public throttledInput$ = this.input$.pipe( + distinctUntilChanged(), + throttleTime(300), + ); + + public output$ = this.throttledInput$.pipe( + switchMap((input) => + this.http + .post( + `${environment.apiUrl}mp/parse`, + { input }, + { + headers: new HttpHeaders({ + "Content-Type": "application/json", + }), + }, + ) + .pipe( + catchError((error) => { + this.errorHandler.handleHttpError( + error, + $localize`An error occurred while handling your input.`, + ); + return of(null); + }), + ), + ), + share(), + ); + + public loading$ = merge( + this.throttledInput$.pipe(map(() => true)), + this.output$.pipe(map(() => false)), + ); + + constructor( + private http: HttpClient, + private errorHandler: ErrorHandlerService, + ) {} +} diff --git a/frontend/src/app/shared/services/status.service.ts b/frontend/src/app/shared/services/status.service.ts index a1164c5..e397837 100644 --- a/frontend/src/app/shared/services/status.service.ts +++ b/frontend/src/app/shared/services/status.service.ts @@ -6,15 +6,17 @@ import { environment } from "src/environments/environment"; interface Status { aethel: boolean; spindle: boolean; + mp: boolean; + vulcan: boolean; } @Injectable({ - providedIn: "root" + providedIn: "root", }) -export class StatusService{ +export class StatusService { constructor(private http: HttpClient) {} - get(): Observable { + public getStatus$(): Observable { return this.http.get(`${environment.apiUrl}status/`); } } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 3e8dffc..05d1a8b 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -14,6 +14,8 @@ import { CommonModule } from "@angular/common"; import { ExportTextComponent } from "./components/spindle-export/export-text/export-text.component"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { SpindleExportComponent } from "./components/spindle-export/spindle-export.component"; +import { MpApiService } from "./services/mp-api.service"; +import { ReactiveFormsModule } from "@angular/forms"; @NgModule({ declarations: [ @@ -25,7 +27,7 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ExportTextComponent, SpindleExportComponent, ], - imports: [CommonModule, FontAwesomeModule], + imports: [CommonModule, FontAwesomeModule, ReactiveFormsModule], providers: [ AethelApiService, AlertService, @@ -33,6 +35,7 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ErrorHandlerService, SpindleApiService, StatusService, + MpApiService, ], exports: [ AlertComponent, @@ -40,6 +43,9 @@ import { SpindleExportComponent } from "./components/spindle-export/spindle-expo ProofPipe, SpindleExportComponent, ExportButtonComponent, + CommonModule, + ReactiveFormsModule, + FontAwesomeModule, ], }) export class SharedModule {} diff --git a/frontend/src/app/spindle/spindle.component.ts b/frontend/src/app/spindle/spindle.component.ts index 00dc4ab..6854acc 100644 --- a/frontend/src/app/spindle/spindle.component.ts +++ b/frontend/src/app/spindle/spindle.component.ts @@ -26,7 +26,7 @@ export class SpindleComponent implements OnInit { spindleReady$ = timer(0, 5000).pipe( takeUntil(this.stopStatus$), - switchMap(() => this.statusService.get()), + switchMap(() => this.statusService.getStatus$()), map((status) => status.spindle), share(), ); From 4aacd2f70f1c0d0264999ff8afe4aff2aa9cd11d Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 20 Nov 2024 16:11:40 +0100 Subject: [PATCH 8/8] Fix failing tests --- .../minimalist-parser-input.component.spec.ts | 7 ++++++- frontend/src/app/shared/services/mp-api.service.spec.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts index 6203f57..7aba97e 100644 --- a/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts +++ b/frontend/src/app/minimalist-parser/minimalist-parser-input/minimalist-parser-input.component.spec.ts @@ -1,13 +1,18 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MinimalistParserInputComponent } from "./minimalist-parser-input.component"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { SharedModule } from "src/app/shared/shared.module"; describe("MinimalistParserInputComponent", () => { let component: MinimalistParserInputComponent; let fixture: ComponentFixture; beforeEach(async () => { - await TestBed.configureTestingModule({}).compileComponents(); + await TestBed.configureTestingModule({ + declarations: [MinimalistParserInputComponent], + imports: [HttpClientTestingModule, SharedModule], + }).compileComponents(); fixture = TestBed.createComponent(MinimalistParserInputComponent); component = fixture.componentInstance; diff --git a/frontend/src/app/shared/services/mp-api.service.spec.ts b/frontend/src/app/shared/services/mp-api.service.spec.ts index e525e6a..708d43b 100644 --- a/frontend/src/app/shared/services/mp-api.service.spec.ts +++ b/frontend/src/app/shared/services/mp-api.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from "@angular/core/testing"; import { MpApiService } from "./mp-api.service"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; describe("MpApiService", () => { let service: MpApiService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); service = TestBed.inject(MpApiService); });