Skip to content

Commit

Permalink
Merge branch 'fix/type-search' into feature/list-pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
XanderVertegaal committed Aug 30, 2024
2 parents f2a4795 + ac98dcb commit dfa1b15
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 41 deletions.
27 changes: 21 additions & 6 deletions backend/aethel_db/views/detail.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from enum import Enum
from dataclasses import dataclass, asdict
from dataclasses import dataclass

from django.http import HttpRequest, JsonResponse
from rest_framework import status
from rest_framework.views import APIView
from spindle.utils import serialize_phrases_with_infix_notation
from spindle.utils import serialize_phrases
from aethel.frontend import Sample

from aethel_db.models import dataset
Expand All @@ -16,7 +16,23 @@ class AethelDetailResult:
name: str
term: str
subset: str
phrases: list[dict]
phrases: list[dict[str, str]]

def serialize(self):
return {
"sentence": self.sentence,
"name": self.name,
"term": self.term,
"subset": self.subset,
"phrases": [
{
"type": phrase["type"],
"displayType": phrase["display_type"],
"items": phrase["items"],
}
for phrase in self.phrases
],
}


class AethelDetailError(Enum):
Expand All @@ -43,18 +59,17 @@ def parse_sample(self, sample: Sample) -> None:
name=sample.name,
term=str(sample.proof.term),
subset=sample.subset,
phrases=serialize_phrases_with_infix_notation(sample.lexical_phrases),
phrases=serialize_phrases(sample.lexical_phrases),
)

def json_response(self) -> JsonResponse:
result = asdict(self.result) if self.result else None
status_code = (
AETHEL_DETAIL_STATUS_CODES[self.error] if self.error else status.HTTP_200_OK
)

return JsonResponse(
{
"result": result,
"result": self.result.serialize() if self.result else None,
"error": self.error,
},
status=status_code,
Expand Down
16 changes: 12 additions & 4 deletions backend/spindle/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from aethel.frontend import LexicalPhrase
from aethel.mill.types import type_prefix, type_repr


def serialize_phrases_with_infix_notation(
def serialize_phrases(
lexical_phrases: list[LexicalPhrase],
) -> list[dict[str, str]]:
"""
Serializes a list of LexicalPhrases in a human-readable infix notation that is already available in Æthel in Type.__repr__.
Serializes a list of LexicalPhrases in a human-readable infix notation that is already available in Æthel in Type.__repr__ or type_repr(). This is used to display the types in the frontend.
The standard JSON serialization of phrases uses a prefix notation for types, which is good for data-exchange purposes (easier parsing) but less ideal for human consumption.
The standard JSON serialization of phrases uses a prefix notation for types, which is good for data-exchange purposes (easier parsing) but less ideal for human consumption. This notation is used to query.
"""
return [dict(phrase.json(), type=repr(phrase.type)) for phrase in lexical_phrases]
return [
dict(
phrase.json(),
display_type=type_repr(phrase.type),
type=type_prefix(phrase.type),
)
for phrase in lexical_phrases
]
21 changes: 15 additions & 6 deletions backend/spindle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
serial_proof_to_json,
)

from spindle.utils import serialize_phrases_with_infix_notation
from spindle.utils import serialize_phrases

http = urllib3.PoolManager()

Expand All @@ -31,7 +31,6 @@
Mode = Literal["latex", "pdf", "overleaf", "term-table", "proof"]



class SpindleErrorSource(Enum):
INPUT = "input"
SPINDLE = "spindle"
Expand All @@ -52,14 +51,24 @@ class SpindleResponse:

def json_response(self) -> JsonResponse:
# TODO: set HTTP error code when error is not None

# Convert display_type to displayType for frontend.
camelized = [
{
**{k: v for k, v in phrase.items() if k != "display_type"},
"displayType": phrase["display_type"],
}
for phrase in self.lexical_phrases
]

return JsonResponse(
{
"latex": self.latex,
"pdf": self.pdf,
"redirect": self.redirect,
"error": self.error.value if self.error else None,
"term": self.term,
"lexical_phrases": self.lexical_phrases,
"lexical_phrases": camelized,
"proof": self.proof,
}
)
Expand Down Expand Up @@ -193,7 +202,7 @@ def overleaf_redirect(self, latex: str) -> JsonResponse:
def term_table_response(self, parsed: ParserResponse) -> JsonResponse:
"""Return the term and the lexical phrases as a JSON response."""

phrases = serialize_phrases_with_infix_notation(parsed.lexical_phrases)
phrases = serialize_phrases(parsed.lexical_phrases)
return SpindleResponse(
term=str(parsed.proof.term),
lexical_phrases=phrases,
Expand All @@ -210,10 +219,10 @@ def spindle_status():
try:
r = http.request(
method="GET",
url=settings.SPINDLE_URL + '/status/',
url=settings.SPINDLE_URL + "/status/",
headers={"Content-Type": "application/json"},
timeout=1,
retries=False
retries=False,
)
return r.status < 400
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/sample/sample.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<span *ngFor="let item of phrase.items">{{ item.word }} </span>
</td>
<td>
<span class="proof" [innerHtml]="phrase.type | proof"></span>
<span class="proof" [innerHtml]="phrase.displayType | proof"></span>
</td>
<td>
@if (showButtons(phrase.items)) {
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/app/sample/sample.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {
import {
AethelDetail,
AethelDetailError,
LexicalPhrase,
AethelDetailPhrase,
} from "../shared/types";
import { By } from "@angular/platform-browser";
import { ProofPipe } from "../shared/pipes/proof.pipe";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";

const fakePhrase: LexicalPhrase = {
const fakePhrase: AethelDetailPhrase = {
type: "cheese->tosti",
displayType: "cheese -> tosti",
items: [
{
word: "cheeses",
Expand Down Expand Up @@ -71,23 +72,23 @@ describe("SampleComponent", () => {

it("should construct a valid route for word search", () => {
const spy = spyOn(router, "navigate");
component.searchAethel(fakePhrase, 'word');
component.searchAethel(fakePhrase, "word");
expect(spy).toHaveBeenCalledOnceWith(["/aethel"], {
queryParams: { word: "cheeses" },
});
});

it("should construct a valid route for type search", () => {
const spy = spyOn(router, "navigate");
component.searchAethel(fakePhrase, 'type');
component.searchAethel(fakePhrase, "type");
expect(spy).toHaveBeenCalledOnceWith(["/aethel"], {
queryParams: { type: "cheese->tosti" },
});
});

it("should construct a valid route for word and type search", () => {
const spy = spyOn(router, "navigate");
component.searchAethel(fakePhrase, 'word-and-type');
component.searchAethel(fakePhrase, "word-and-type");
expect(spy).toHaveBeenCalledOnceWith(["/aethel"], {
queryParams: { word: "cheeses", type: "cheese->tosti" },
});
Expand Down
11 changes: 7 additions & 4 deletions frontend/src/app/sample/sample.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { AethelApiService } from "../shared/services/aethel-api.service";
import { map } from "rxjs";
import { AethelMode, LexicalPhrase } from "../shared/types";
import { AethelMode, AethelDetailPhrase } from "../shared/types";
import { isNonNull } from "../shared/operators/IsNonNull";
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
import { Location } from "@angular/common";
Expand Down Expand Up @@ -31,12 +31,12 @@ export class SampleComponent {
private location: Location,
) {}

public searchAethel(phrase: LexicalPhrase, mode: AethelMode): void {
public searchAethel(phrase: AethelDetailPhrase, mode: AethelMode): void {
const queryParams = this.formatQueryParams(phrase, mode);
this.router.navigate(["/aethel"], { queryParams });
}

public showButtons(items: LexicalPhrase["items"]): boolean {
public showButtons(items: AethelDetailPhrase["items"]): boolean {
// Buttons are hidden if the phrase contains too few characters.
const combined = items.map((item) => item.word).join(" ");
return combined.length > 2;
Expand All @@ -46,7 +46,10 @@ export class SampleComponent {
this.location.back();
}

private formatQueryParams(phrase: LexicalPhrase, mode: AethelMode): Params {
private formatQueryParams(
phrase: AethelDetailPhrase,
mode: AethelMode,
): Params {
const queryParams: Params = {};
if (mode === "word" || mode === "word-and-type") {
queryParams["word"] = phrase.items
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/app/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ type LexicalItem = {
lemma: string;
};

export type LexicalPhrase = {
export type AethelDetailPhrase = {
items: LexicalItem[];
type: string;
displayType: string;
};

// Should correspond with SpindleResponse dataclass in backend.
Expand All @@ -32,7 +33,7 @@ export interface SpindleReturn {
pdf: string | null;
redirect: string | null;
term: string | null;
lexical_phrases: LexicalPhrase[];
lexical_phrases: AethelDetailPhrase[];
proof: Record<string, unknown> | null;
}

Expand All @@ -51,11 +52,11 @@ export interface AethelListLexicalItem {
}

export interface AethelListPhrase {
items: AethelListLexicalItem[]
items: AethelListLexicalItem[];
}

export interface AethelListResult {
phrase: AethelListPhrase
phrase: AethelListPhrase;
type: string;
displayType: string;
sampleCount: number;
Expand All @@ -79,7 +80,7 @@ export interface AethelDetailResult {
name: string;
term: string;
subset: string;
phrases: LexicalPhrase[];
phrases: AethelDetailPhrase[];
}

export interface AethelDetail {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/spindle/spindle.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ <h4 i18n>Term:</h4>
<td>
<span *ngFor="let item of phrase.items">{{ item.word }} </span>
</td>
<td [innerHtml]="phrase.type | proof"></td>
<td [innerHtml]="phrase.displayType | proof"></td>
</tr>
</table>
</div>
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/app/spindle/spindle.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ErrorHandlerService } from "../shared/services/error-handler.service";
import { AlertService } from "../shared/services/alert.service";
import { AlertType } from "../shared/components/alert/alert.component";
import { faDownload, faCopy } from "@fortawesome/free-solid-svg-icons";
import { LexicalPhrase, SpindleMode } from "../shared/types";
import { AethelDetailPhrase, SpindleMode } from "../shared/types";
import { SpindleApiService } from "../shared/services/spindle-api.service";
import { Subject, filter, map, share, switchMap, takeUntil, timer } from "rxjs";
import { StatusService } from "../shared/services/status.service";
Expand All @@ -26,7 +26,7 @@ export class SpindleComponent implements OnInit {
});
term: string | null = null;
textOutput: TextOutput | null = null;
lexicalPhrases: LexicalPhrase[] = [];
lexicalPhrases: AethelDetailPhrase[] = [];
loading$ = this.apiService.loading$;

faCopy = faCopy;
Expand All @@ -37,23 +37,25 @@ export class SpindleComponent implements OnInit {
spindleReady$ = timer(0, 5000).pipe(
takeUntil(this.stopStatus$),
switchMap(() => this.statusService.get()),
map(status => status.spindle),
share()
map((status) => status.spindle),
share(),
);

constructor(
private apiService: SpindleApiService,
private alertService: AlertService,
private errorHandler: ErrorHandlerService,
private destroyRef: DestroyRef,
private statusService: StatusService
private statusService: StatusService,
) {}

ngOnInit(): void {
this.spindleReady$.pipe(
filter(ready => ready === true),
takeUntilDestroyed(this.destroyRef)
).subscribe(() => this.stopStatus$.next());
this.spindleReady$
.pipe(
filter((ready) => ready === true),
takeUntilDestroyed(this.destroyRef),
)
.subscribe(() => this.stopStatus$.next());

this.apiService.output$
.pipe(takeUntilDestroyed(this.destroyRef))
Expand Down

0 comments on commit dfa1b15

Please sign in to comment.