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

CrossDoc: full functions implemented #152

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
5 changes: 4 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ raw_data/
src/__tests__/
src/declaration.d.ts
src/serviceWorker.js
src/setupProxy.js
src/setupProxy.js
src/crossviewer
src/app/pages/CrossDoc.tsx

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"react-is": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-select": "^3.0.8",
"react-modal": "^3.11.2",
"react-alert": "^7.0.2",
"react-alert-template-basic": "^1.0.0",
"styled-components": "^5.1.1"
},
"devDependencies": {
Expand Down
170 changes: 170 additions & 0 deletions simple-backend/nlpviewer_backend/handlers/crossdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,93 @@
from copy import deepcopy
from datetime import datetime

from ..lib.utils import format_forte_id

default_type = "edu.cmu.CrossEventRelation"

def read_creation_record(textPack):
"""
Read teh creation record of the forte json file
Get a mapping from username to a set of tids
"""
mapping = {} # from username/forteid to their creation records
for username in textPack["py/state"]["creation_records"]:
tids = set(textPack["py/state"]["creation_records"][username]["py/set"])
mapping[username] = tids
return mapping

def delete_link(textPack, parent_event_id, child_event_id, forteID):
"""
Delete both link and its creation record
This function does not return, it did operations on the original textPack
"""
mapping = read_creation_record(textPack)
tid_to_delete = None
index_to_delete = None

# delete by iterating all, and record down the wanted ones, skip the deleted one
for index, item in enumerate(textPack["py/state"]["links"]):
if item["py/state"]["_parent"]["py/tuple"][1] == parent_event_id and \
item["py/state"]["_child"]["py/tuple"][1] == child_event_id and \
forteID in mapping and \
item["py/state"]["_tid"] in mapping[forteID]:
tid_to_delete = item["py/state"]["_tid"]
index_to_delete = index

if tid_to_delete is not None:
del textPack["py/state"]["links"][index_to_delete]
textPack["py/state"]["creation_records"][forteID]["py/set"].remove(tid_to_delete)



def format_cross_doc_helper(uploaded_link, next_tid):
"""
format the cross doc link uploaded from the frontend

"""
link = deepcopy(uploaded_link)
del link["py/state"]['coref_question_answers']

link["py/object"] = default_type
link["py/state"]['_tid'] = next_tid
link["py/state"]["_embedding"] = []

# coref
link["py/state"]["coref_questions"] = {
"py/object": "forte.data.ontology.core.FList",
"py/state": {
"_FList__data": []
}
}
link["py/state"]["coref_answers"] = []
for item in uploaded_link["py/state"]["coref_question_answers"]:
link["py/state"]["coref_questions"]["py/state"]["_FList__data"].append(
{
"py/object": "forte.data.ontology.core.Pointer",
"py/state": {
"_tid": item["question_id"]
}
})
link["py/state"]["coref_answers"].append(item["option_id"])

return link

def find_and_advance_next_tid(textPackJson):
"""
find the global maximum tid and return tid+1
"""
textPackJson['py/state']['serialization']["next_id"] += 1
return textPackJson['py/state']['serialization']["next_id"] - 1

def extract_doc_id_from_crossdoc(cross_doc):
text_pack = json.loads(cross_doc.textPack)
doc_external_ids = text_pack["py/state"]["_pack_ref"]
doc_external_id_0 = doc_external_ids[0]
doc_external_id_1 = doc_external_ids[1]
doc_0 = cross_doc.project.documents.get(packID=doc_external_id_0)
doc_1 = cross_doc.project.documents.get(packID=doc_external_id_1)
return doc_0, doc_1



def listAll(request):
Expand All @@ -37,3 +124,86 @@ def delete(request, crossdoc_id):

return HttpResponse('ok')


def query(request, crossdoc_id):
cross_doc = CrossDoc.objects.get(pk=crossdoc_id)
doc_0, doc_1 = extract_doc_id_from_crossdoc(cross_doc)
parent = {
'id': doc_0.pk,
'textPack': doc_0.textPack,
'ontology': doc_0.project.ontology
}
child = {
'id': doc_1.pk,
'textPack': doc_1.textPack,
'ontology': doc_1.project.ontology
}
forteID = format_forte_id(request.user.pk)
to_return = {"crossDocPack":model_to_dict(cross_doc),"_parent": parent, "_child":child, "forteID":forteID}
return JsonResponse(to_return, safe=False)


def new_cross_doc_link(request, crossdoc_id):

crossDoc = CrossDoc.objects.get(pk=crossdoc_id)
docJson = model_to_dict(crossDoc)
textPackJson = json.loads(docJson['textPack'])
forteID = format_forte_id(request.user.pk)

received_json_data = json.loads(request.body)
data = received_json_data.get('data')
link = data["link"]

link_id = find_and_advance_next_tid(textPackJson)
link = format_cross_doc_helper(link, link_id)

# delete possible duplicate link before and the creation records
parent_event_id = link["py/state"]["_parent"]["py/tuple"][1]
child_event_id = link["py/state"]["_child"]["py/tuple"][1]
delete_link(textPackJson, parent_event_id, child_event_id, forteID)

# append new link to the textpack
textPackJson['py/state']['links'].append(link)

# append the creation records
if forteID not in textPackJson["py/state"]["creation_records"]:
textPackJson["py/state"]["creation_records"][forteID] = {"py/set":[]}
textPackJson["py/state"]["creation_records"][forteID]["py/set"].append(link_id)

# commit to the database
crossDoc.textPack = json.dumps(textPackJson)
crossDoc.save()
return JsonResponse({"crossDocPack": model_to_dict(crossDoc)}, safe=False)


def delete_cross_doc_link(request, crossdoc_id, link_id):
"""
request handler, delete by tid
"""

crossDoc = CrossDoc.objects.get(pk=crossdoc_id)
docJson = model_to_dict(crossDoc)
textPackJson = json.loads(docJson['textPack'])
forteID = format_forte_id(request.user.pk)

deleteIndex = -1
success = False
for index, item in enumerate(textPackJson['py/state']['links']):
if item["py/state"]['_tid'] == link_id:
deleteIndex = index
success = True

if deleteIndex == -1:
success = False
else:
del textPackJson['py/state']['links'][deleteIndex]
textPackJson["py/state"]["creation_records"][forteID]["py/set"].remove(link_id)
crossDoc.textPack = json.dumps(textPackJson)
crossDoc.save()

return JsonResponse({"crossDocPack": model_to_dict(crossDoc), "update_success": success}, safe=False)





4 changes: 3 additions & 1 deletion simple-backend/nlpviewer_backend/handlers/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ def create(request):
project_id = received_json_data.get('project_id')
project = Project.objects.get(id=project_id)
check_perm_project(project, request.user, 'nlpviewer_backend.new_project')

pack_json = json.loads(received_json_data.get('textPack'))
pack_id = int(pack_json["py/state"]["meta"]["py/state"]["_pack_id"])
doc = Document(
name=received_json_data.get('name'),
packID=pack_id,
textPack=received_json_data.get('textPack'),
project = Project.objects.get(
pk=project_id
Expand Down
8 changes: 8 additions & 0 deletions simple-backend/nlpviewer_backend/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ def fetch_project_check_perm(id, user, perm):

return project


def format_forte_id(id):
"""

convert the user id (pk) to stave id. Used for crossdoc annotation creation record.
"""
return "stave." + str(id)

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.0.4 on 2021-01-15 21:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('nlpviewer_backend', '0011_auto_20210113_2148'),
]

operations = [
migrations.AddField(
model_name='crossdoc',
name='packID',
field=models.IntegerField(null=True, unique=True),
),
migrations.AddField(
model_name='document',
name='packID',
field=models.IntegerField(null=True, unique=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2021-01-16 01:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('nlpviewer_backend', '0012_auto_20210115_1610'),
]

operations = [
migrations.AlterField(
model_name='document',
name='packID',
field=models.IntegerField(null=True),
),
]
3 changes: 3 additions & 0 deletions simple-backend/nlpviewer_backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Document(models.Model):
# content: textPack: text body + annotation

name = models.CharField(max_length=200)
packID = models.IntegerField(null=True)

# relationship: project
project = models.ForeignKey(
Expand All @@ -55,6 +56,8 @@ class Document(models.Model):
class CrossDoc(models.Model):

name = models.CharField(max_length=200)
packID = models.IntegerField(unique = True, null=True)


# relationship: project
project = models.ForeignKey(
Expand Down
6 changes: 6 additions & 0 deletions simple-backend/nlpviewer_backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
path('next_doc/<int:document_id>', document.get_next_document_id),
path('prev_doc/<int:document_id>', document.get_prev_document_id),

path('crossdocs/new', crossdoc.create),
path('crossdocs/<int:crossdoc_id>/delete', crossdoc.delete),
path('crossdocs/<int:crossdoc_id>', crossdoc.query),
path('crossdocs/<int:crossdoc_id>/links/new', crossdoc.new_cross_doc_link),
path('crossdocs/<int:crossdoc_id>/links/<int:link_id>/delete', crossdoc.delete_cross_doc_link),

path('projects/all', project.listAll),
path('projects', project.list_user_projects),
path('projects/new', project.create),
Expand Down
5 changes: 5 additions & 0 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {BrowserRouter as Router, Switch, Route, Link} from 'react-router-dom';
import Login from './pages/Login';
import SignUp from './pages/SignUp';
import Viewer from './pages/Viewer';
import CrossDoc from './pages/CrossDoc';
import Projects from './pages/Projects';
import Project from './pages/Project';
import Users from './pages/Users';
Expand Down Expand Up @@ -49,6 +50,10 @@ function App() {
<Viewer />
</Route>

<Route path="/crossdocs/:id">
<CrossDoc />
</Route>

<Route path="/projects">
<Projects />
</Route>
Expand Down
33 changes: 33 additions & 0 deletions src/app/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ export interface APIDocConfig {
config: string;
}

interface APICrossDocPack {
id: string;
textPack: string;
}
interface APICrossDoc {
crossDocPack: APICrossDocPack;
_parent: APIDocument;
_child: APIDocument;
nextCrossDocId: string;
forteID: string;
nextID: string;
secret_code: string;
}

export function fetchDocuments(): Promise<any> {
return fetch('/api/documents').then(r => r.json());
}
Expand Down Expand Up @@ -197,6 +211,25 @@ export function deleteLink(documentId: string, linkId: string) {
return postData(`/api/documents/${documentId}/links/${linkId}/delete`, {});
}

export function fetchCrossDoc(id: string): Promise<APICrossDoc> {
return fetch(`/api/crossdocs/${id}`).then(r => r.json());
}
export function addCrossLink(crossDocID: string, data: any) {
return postData(`/api/crossdocs/${crossDocID}/links/new`, {
data,
}).then(r => r.json());
}

export function deleteCrossLink(crossDocID: string, linkID: string) {
return postData(
`/api/crossdocs/${crossDocID}/links/${linkID}/delete`
).then(r => r.json());
}

export function nextCrossDoc() {
return postData('/api/crossdocs/next-crossdoc', {}).then(r => r.json());
}

export function loadNlpModel(modelName: string) {
return postData(`/api/nlp/load/${modelName}`, {});
}
Expand Down
Loading