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

refactor: add types to find_one_entry routes + start refactor PropertyTable #481

Merged
merged 5 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(() => {
eric-nguyen-cs marked this conversation as resolved.
Show resolved Hide resolved
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
Loading