Skip to content
Open
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
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