Skip to content

Commit

Permalink
refactor: add types to find_one_entry routes + start refactor Propert…
Browse files Browse the repository at this point in the history
…yTable (#481)

* feat: add EntryNode type for find_one_entry route

* fix: update frontend to handle new API

* fix: lint backend

* fix: respond to comment
  • Loading branch information
eric-nguyen-cs authored Apr 17, 2024
1 parent 6003639 commit cc7df50
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 89 deletions.
12 changes: 4 additions & 8 deletions backend/editor/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
from . import graph_db

# Controller imports
from .controllers import project_controller, search_controller
from .controllers import node_controller, project_controller, search_controller
from .entries import TaxonomyGraph

# Custom exceptions
from .exceptions import GithubBranchExistsError, GithubUploadError

# Data model imports
from .models.node_models import EntryNodeCreate, ErrorNode, Footer, Header, NodeType
from .models.node_models import EntryNode, EntryNodeCreate, ErrorNode, Footer, Header, NodeType
from .models.project_models import Project, ProjectEdit, ProjectStatus
from .models.search_models import EntryNodeSearchResult
from .scheduler import scheduler_lifespan
Expand Down Expand Up @@ -199,16 +199,12 @@ async def find_all_root_nodes(response: Response, branch: str, taxonomy_name: st


@app.get("/{taxonomy_name}/{branch}/entry/{entry}")
async def find_one_entry(response: Response, branch: str, taxonomy_name: str, entry: str):
async def find_one_entry(branch: str, taxonomy_name: str, entry: str) -> EntryNode:
"""
Get entry corresponding to id within taxonomy
"""
taxonomy = TaxonomyGraph(branch, taxonomy_name)
one_entry = await taxonomy.get_nodes("ENTRY", entry)

check_single(one_entry)

return one_entry[0]["n"]
return await node_controller.get_entry_node(taxonomy.project_name, entry)


@app.get("/{taxonomy_name}/{branch}/entry/{entry}/parents")
Expand Down
19 changes: 18 additions & 1 deletion backend/editor/controllers/node_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from openfoodfacts_taxonomy_parser import utils as parser_utils

from ..graph_db import get_current_transaction
from ..models.node_models import EntryNodeCreate, ErrorNode
from ..models.node_models import EntryNode, EntryNodeCreate, ErrorNode
from .utils.result_utils import get_unique_record


async def delete_project_nodes(project_id: str):
Expand Down Expand Up @@ -45,6 +46,22 @@ async def create_entry_node(
return (await result.data())[0]["n.id"]


async def get_entry_node(project_id: str, node_id: str) -> EntryNode:
query = (
f"""
MATCH (n:{project_id}:ENTRY
"""
+ """{id: $node_id})
RETURN n
"""
)
params = {"node_id": node_id}
result = await get_current_transaction().run(query, params)

entry_record = await get_unique_record(result, node_id)
return EntryNode(**entry_record["n"])


async def get_error_node(project_id: str) -> ErrorNode | None:
query = """
MATCH (n:ERRORS {id: $project_id})
Expand Down
9 changes: 3 additions & 6 deletions backend/editor/controllers/project_controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from fastapi import HTTPException

from ..graph_db import get_current_transaction
from ..models.project_models import Project, ProjectCreate, ProjectEdit, ProjectStatus
from .node_controller import delete_project_nodes
from .utils.result_utils import get_unique_record


async def get_project(project_id: str) -> Project:
Expand All @@ -15,10 +14,8 @@ async def get_project(project_id: str) -> Project:
"""
params = {"project_id": project_id}
result = await get_current_transaction().run(query, params)
project = await result.single()
if project is None:
raise HTTPException(status_code=404, detail="Project not found")
return Project(**project["p"])
project_record = await get_unique_record(result, project_id)
return Project(**project_record["p"])


async def get_projects_by_status(status: ProjectStatus) -> list[Project]:
Expand Down
28 changes: 28 additions & 0 deletions backend/editor/controllers/utils/result_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from fastapi import HTTPException
from neo4j import AsyncResult, Record


async def get_unique_record(result: AsyncResult, record_id: str | None = None) -> Record:
"""
Gets the unique record from a Cypher query result
Raises:
404 HTTPException: If no record is found
500 HTTPException: If multiple records are found
"""
record = await result.fetch(1)
if record is None:
exception_message = f"Record {record_id} not found" if record_id else "Record not found"
raise HTTPException(status_code=404, detail=exception_message)

remaining_record = await result.peek()
if remaining_record is not None:
exception_message = (
f"Multiple records with id {record_id} found" if record_id else "Multiple records found"
)
raise HTTPException(
status_code=500,
detail=exception_message,
)

return record[0]
1 change: 1 addition & 0 deletions backend/editor/models/node_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class EntryNode(BaseModel):
properties: dict[str, str]
comments: dict[str, list[str]]
is_external: bool = False
original_taxonomy: str | None = None

@model_validator(mode="before")
@classmethod
Expand Down
10 changes: 9 additions & 1 deletion backend/openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,11 @@
"responses": {
"200": {
"description": "Successful Response",
"content": { "application/json": { "schema": {} } }
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/EntryNode" }
}
}
},
"422": {
"description": "Validation Error",
Expand Down Expand Up @@ -1217,6 +1221,10 @@
"type": "boolean",
"title": "Isexternal",
"default": false
},
"originalTaxonomy": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Originaltaxonomy"
}
},
"type": "object",
Expand Down
17 changes: 17 additions & 0 deletions taxonomy-editor-frontend/src/backend-types/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
/**
* @deprecated Migrate to @/client/models/EntryNode when possible
*/
export type DestructuredEntryNode = {
id: string;
precedingLines: Array<string>;
srcPosition: number;
mainLanguage: string;
isExternal: boolean;
originalTaxonomy: string | null;
// TODO: Use updated types from the API
[key: string]: any;
// tags: Record<string, Array<string>>;
// properties: Record<string, string>;
// comments: Record<string, Array<string>>;
};

export type ParentsAPIResponse = string[];
1 change: 1 addition & 0 deletions taxonomy-editor-frontend/src/client/models/EntryNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type EntryNode = {
properties: Record<string, string>;
comments: Record<string, Array<string>>;
isExternal: boolean;
originalTaxonomy: string | null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* tslint:disable */
/* eslint-disable */
import type { Body_upload_taxonomy__taxonomy_name___branch__upload_post } from "../models/Body_upload_taxonomy__taxonomy_name___branch__upload_post";
import type { EntryNode } from "../models/EntryNode";
import type { EntryNodeCreate } from "../models/EntryNodeCreate";
import type { EntryNodeSearchResult } from "../models/EntryNodeSearchResult";
import type { ErrorNode } from "../models/ErrorNode";
Expand Down Expand Up @@ -180,14 +181,14 @@ export class DefaultService {
* @param branch
* @param taxonomyName
* @param entry
* @returns any Successful Response
* @returns EntryNode Successful Response
* @throws ApiError
*/
public static findOneEntryTaxonomyNameBranchEntryEntryGet(
branch: string,
taxonomyName: string,
entry: string
): CancelablePromise<any> {
): CancelablePromise<EntryNode> {
return __request(OpenAPI, {
method: "GET",
url: "/{taxonomy_name}/{branch}/entry/{entry}",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Alert, Box, Snackbar, Typography, Button } from "@mui/material";
import SaveIcon from "@mui/icons-material/Save";
import CircularProgress from "@mui/material/CircularProgress";
import { useState } from "react";
import { useMemo, useState } from "react";
import ListEntryParents from "./ListEntryParents";
import ListEntryChildren from "./ListEntryChildren";
import { ListTranslations } from "./ListTranslations";
Expand All @@ -12,6 +12,7 @@ import { createURL, getNodeType, toSnakeCase } from "@/utils";
import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { DefaultService } from "@/client";
import { DestructuredEntryNode } from "@/backend-types/types";

interface AccumulateAllComponentsProps {
id: string;
Expand All @@ -38,7 +39,7 @@ const AccumulateAllComponents = ({
const isEntry = getNodeType(id) === "entry";

const {
data: node,
data: rawNode,
isPending,
isError,
error,
Expand All @@ -58,8 +59,29 @@ const AccumulateAllComponents = ({
);
},
});
const [nodeObject, setNodeObject] = useState(null); // Storing updates to node
const [originalNodeObject, setOriginalNodeObject] = useState(null); // For tracking changes

// Intermediate destructuring step. Migrate to @/client/models/EntryNode when possible
const node: DestructuredEntryNode | null = useMemo(() => {
if (!rawNode) return null;
const {
tags: _tags,
properties: _properties,
comments: _comments,
...destructuredNode
} = {
...rawNode,
...rawNode.tags,
...rawNode.properties,
...rawNode.comments,
};
return destructuredNode;
}, [rawNode]);

const [nodeObject, setNodeObject] = useState<DestructuredEntryNode | null>(
null
); // Storing updates to node
const [originalNodeObject, setOriginalNodeObject] =
useState<DestructuredEntryNode | null>(null); // For tracking changes
const [updateChildren, setUpdateChildren] = useState(null); // Storing updates of children in node
const [previousUpdateChildren, setPreviousUpdateChildren] = useState(null); // Tracking changes of children
const [open, setOpen] = useState(false); // Used for Dialog component
Expand Down Expand Up @@ -118,8 +140,7 @@ const AccumulateAllComponents = ({
// Function handling updation of node
const handleSubmit = () => {
if (!nodeObject) return;
const data = Object.assign({}, nodeObject);
delete data["id"]; // ID not allowed in POST
const { id: _, ...data } = { ...nodeObject }; // ID not allowed in POST

const dataToBeSent = {};
// Remove UUIDs from data
Expand Down
Loading

0 comments on commit cc7df50

Please sign in to comment.