Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: MP parser frontend scaffolding + API calls #84

Merged
merged 10 commits into from
Jan 9, 2025
2 changes: 1 addition & 1 deletion backend/aethel_db/views/status.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from aethel_db.models import dataset


def aethel_status():
def aethel_status() -> bool:
return dataset is not None
Empty file.
3 changes: 3 additions & 0 deletions backend/minimalist_parser/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/minimalist_parser/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class MinimalistParserConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'minimalist_parser'
Empty file.
3 changes: 3 additions & 0 deletions backend/minimalist_parser/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
3 changes: 3 additions & 0 deletions backend/minimalist_parser/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
5 changes: 5 additions & 0 deletions backend/minimalist_parser/urls.py
Original file line number Diff line number Diff line change
@@ -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")]
8 changes: 8 additions & 0 deletions backend/minimalist_parser/views/parse.py
Original file line number Diff line number Diff line change
@@ -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!"})
Empty file.
4 changes: 4 additions & 0 deletions backend/parseport/http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import urllib3


http_client = urllib3.PoolManager()
XanderVertegaal marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions backend/parseport/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"revproxy",
"corsheaders",
"aethel_db",
"minimalist_parser",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -123,6 +124,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/"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in use yet, so don't try to connect to them ;)

VULCAN_URL = f"http://pp-vulcan:32771/"

data_subset_path = "./aethel_db/data/aethel_subset.pickle"
full_dataset_path = "/data/aethel.pickle"
Expand Down
2 changes: 2 additions & 0 deletions backend/parseport/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,6 +36,7 @@
path("api/status/", StatusView.as_view(), name="status"),
path("api/spindle/<str:mode>", SpindleView.as_view(), name="spindle"),
path("api/aethel/", include("aethel_db.urls")),
path("api/mp/", include("minimalist_parser.urls")),
path(
"api-auth/",
include(
Expand Down
39 changes: 34 additions & 5 deletions backend/parseport/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes very functional very nice

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=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'),
)
)
21 changes: 3 additions & 18 deletions backend/spindle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
)

from spindle.utils import serialize_phrases

http = urllib3.PoolManager()
from parseport.http_client import http_client


# Output mode
Expand Down Expand Up @@ -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}),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion frontend/src/app/aethel/aethel.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ <h2 class="title is-2" i18n>Æthel</h2>
start.
</p>

@if (status$ | async) {
@if (statusOk$ | async) {
<form class="form" [formGroup]="form" (ngSubmit)="submitWord()">
<div class="field">
<label for="aethel-input" class="label">Search:</label>
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/app/aethel/aethel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -44,7 +44,9 @@ export class AethelComponent implements OnInit {
chevronDown: faChevronDown,
};

public status$ = new Subject<boolean>();
public statusOk$ = this.statusService.getStatus$().pipe(
map((status) => status.aethel),
);

constructor(
private apiService: AethelApiService,
Expand All @@ -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) => {
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -46,6 +47,11 @@ import { SharedModule } from "./shared/shared.module";
FontAwesomeModule,
TableModule,
SharedModule,
MinimalistParserModule
],
exports: [
FontAwesomeModule,
SharedModule,
],
bootstrap: [AppComponent],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,43 @@ <h1 class="title" i18n>Minimalist Parser</h1>

<p i18n>Please enter a sentence below to begin.</p>

@if (statusOk$ | async) {
<section class="section">
<div class="field">
<label for="minimalist-parser-input" class="label" i18n>
Enter a sentence
</label>
<div class="control">
<input id="minimalist-parser-input" type="text" class="input" placeholder="Enter a sentence" i18n-placeholder>
</div>
<form class="form" [formGroup]="form" (ngSubmit)="parse()">
<label for="minimalist-parser-input" class="label" i18n>
Enter a sentence
</label>
<div class="control-wrapper">
<div class="control">
<input
id="minimalist-parser-input"
type="text"
class="input"
[class.is-danger]="form.touched && form.invalid"
[formControl]="form.controls.mpInput"
placeholder="Enter a sentence"
i18n-placeholder
/>
</div>
<button
type="submit"
class="button is-primary"
[class.is-loading]="loading$ | async"
>
<span i18n>Submit</span>
</button>
</div>
@if (form.touched && form.invalid) {
<p class="is-size-7 has-text-danger" i18n>
Please enter a sentence first.
</p>
}
</form>
</div>
</section>


} @else {
<p class="notification mt-6" i18n>
The Minimalist Parser is temporarily unavailable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the 'temporarily'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's important to give your users hope and prospects of a better future ;)

</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.control-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}

.control {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -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<MinimalistParserInputComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({}).compileComponents();
await TestBed.configureTestingModule({
declarations: [MinimalistParserInputComponent],
imports: [HttpClientTestingModule, SharedModule],
}).compileComponents();

fixture = TestBed.createComponent(MinimalistParserInputComponent);
component = fixture.componentInstance;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string>("", {
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, considering the exportResult function from spindle.component.ts:

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);
    }

These functions are very similar: could we think of a good way to make them generalizable / put them in a service? Is that even worthwhile, do you think? I'm happy to let this rest since it's not really priority but I was wondering all the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely there is room to DRY this code a bit more. I've created a new issue for it: #93 .

this.form.controls.mpInput.updateValueAndValidity();
const input = this.form.controls.mpInput.value;
if (this.form.invalid || !input) {
return;
}
this.apiService.input$.next(input);
}
}
Original file line number Diff line number Diff line change
@@ -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: [
Expand All @@ -14,6 +14,6 @@ import { MinimalistParserBrowserComponent } from "./minimalist-parser-browser/mi
MinimalistParserReferencesComponent,
MinimalistParserBrowserComponent,
],
imports: [CommonModule],
imports: [SharedModule],
})
export class MinimalistParserModule {}
Loading
Loading