Skip to content
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
14 changes: 11 additions & 3 deletions config/graphql/graphene_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,18 @@ class Meta:


class AnalysisType(AnnotatePermissionsForReadMixin, DjangoObjectType):
full_annotation_list = graphene.List(AnnotationType)
full_annotation_list = graphene.List(
AnnotationType,
document_id=graphene.ID(),
)

def resolve_full_annotation_list(self, info, document_id=None):

def resolve_full_annotation_list(self, info):
return self.annotations.all()
results = self.annotations.all()
if document_id is not None:
document_pk = from_global_id(document_id)[1]
results = results.filter(document_id=document_pk)
return results

class Meta:
model = Analysis
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/assets/configurations/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const VERSION_TAG = "v2.3.0";
export const VERSION_TAG = "v2.3.1";
export const MOBILE_VIEW_BREAKPOINT = 600;
1 change: 1 addition & 0 deletions frontend/src/components/annotator/DocumentAnnotator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ export const DocumentAnnotator = ({
fetchAnnotationsForAnalysis({
variables: {
analysisId: selected_analysis.id,
documentId: opened_document.id,
},
}).then(({ data }) => {
// TODO - properly parse resulting annotation data
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1520,14 +1520,15 @@ export const GET_DATACELLS_FOR_EXTRACT = gql`

export interface GetAnnotationsForAnalysisInput {
analysisId: string;
documentId?: string;
}

export interface GetAnnotationsForAnalysisOutput {
analysis: AnalysisType;
}

export const GET_ANNOTATIONS_FOR_ANALYSIS = gql`
query GetAnnotationsForAnalysis($analysisId: ID!) {
query GetAnnotationsForAnalysis($analysisId: ID!, $documentId: ID) {
analysis(id: $analysisId) {
id
analyzer {
Expand All @@ -1543,7 +1544,7 @@ export const GET_ANNOTATIONS_FOR_ANALYSIS = gql`
labelType
}
}
fullAnnotationList {
fullAnnotationList(documentId: $documentId) {
id
annotationLabel {
id
Expand Down
17 changes: 16 additions & 1 deletion opencontractserver/analyzer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,19 @@ class AnalyzerAdmin(GuardedModelAdmin):

@admin.register(Analysis)
class AnalysisAdmin(GuardedModelAdmin):
list_display = ["id"]
list_display = ["id", "analysis_started", "analysis_completed", "status"]
search_fields = [
"id",
"analyzer__id",
"analyzed_corpus__title",
"creator__username",
]
list_filter = ("status", "created", "analysis_started", "analysis_completed")
raw_id_fields = (
"analyzer",
"analyzed_corpus",
"corpus_action",
"creator",
"analyzed_documents",
)
date_hierarchy = "created"
4 changes: 3 additions & 1 deletion opencontractserver/analyzer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ class Meta:
analysis_started = django.db.models.DateTimeField(blank=True, null=True)
analysis_completed = django.db.models.DateTimeField(blank=True, null=True)
status = django.db.models.CharField(
max_length=24, choices=JobStatus.choices(), default=JobStatus.CREATED.value
max_length=24,
choices=[(status.value, status.name) for status in JobStatus],
default=JobStatus.CREATED.value,
)


Expand Down
14 changes: 12 additions & 2 deletions opencontractserver/annotations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@
@admin.register(Annotation)
class AnnotationAdmin(GuardedModelAdmin):
list_display = ["id", "page", "raw_text", "annotation_label"]
list_filter = ("analysis",)
search_fields = ("id", "raw_text")
search_fields = ["id", "raw_text", "annotation_label__text", "document__title"]
list_filter = ("analysis", "page", "structural", "created", "modified", "is_public")
raw_id_fields = ("annotation_label", "document", "corpus", "analysis", "creator")


@admin.register(Relationship)
class RelationshipAdmin(GuardedModelAdmin):
list_display = ["id", "relationship_label"]
raw_id_fields = (
"relationship_label",
"corpus",
"document",
"source_annotations",
"target_annotations",
"analyzer",
"creator",
)


@admin.register(AnnotationLabel)
Expand Down
36 changes: 35 additions & 1 deletion opencontractserver/corpuses/admin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
from django.contrib import admin
from django.utils.safestring import mark_safe
from guardian.admin import GuardedModelAdmin

from opencontractserver.corpuses.models import Corpus, CorpusAction, CorpusQuery
from opencontractserver.tasks.permissioning_tasks import make_corpus_public_task


@admin.register(Corpus)
class CorpusAdmin(GuardedModelAdmin):
list_display = ["id", "title", "description", "backend_lock", "user_lock"]
list_display_links = ["id", "title"]
list_select_related = ("creator", "label_set")
list_display = [
"id",
"display_icon",
"is_public",
"allow_comments",
"title",
"description",
"backend_lock",
"user_lock",
]
search_fields = ["id", "title", "description", "creator__username"]
list_filter = ("is_public", "created", "modified", "error", "backend_lock")
actions = ["make_public"]
raw_id_fields = ("creator", "user_lock", "documents", "label_set")
date_hierarchy = "created"

def display_icon(self, obj):
if obj.icon:
return mark_safe(f'<img src="{obj.icon.url}" width="50" height="50" />')
return "No icon"

display_icon.short_description = "Icon"

def make_public(self, request, queryset):
for corpus in queryset:
make_corpus_public_task.si(corpus_id=corpus.pk).apply_async()
self.message_user(
request, f"Started making {queryset.count()} corpus(es) public."
)

make_public.short_description = "Make selected corpuses public"


@admin.register(CorpusAction)
Expand Down
6 changes: 6 additions & 0 deletions opencontractserver/documents/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

@admin.register(Document)
class DocumentAdmin(GuardedModelAdmin):
list_display_links = ["id", "title"]
list_select_related = ("creator",)
list_display = ["id", "title", "description", "backend_lock", "user_lock"]
search_fields = ["id", "title", "description", "creator__username"]
list_filter = ("is_public", "created", "modified", "page_count")
raw_id_fields = ("creator",)
date_hierarchy = "created"


@admin.register(DocumentAnalysisRow)
Expand Down
3 changes: 2 additions & 1 deletion opencontractserver/tasks/doc_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ def set_doc_lock_state(*args, locked: bool, doc_id: int):
autoretry_for=(Exception,), retry_backoff=True, retry_kwargs={"max_retries": 5}
)
def nlm_ingest_pdf(user_id: int, doc_id: int) -> list[tuple[int, str]]:

# TODO - seeing persistent failure of Thomas Foster Appelant vs ... and Caster Hinckley et all. vs...
# need to investigate why parser keeps failing on these two.
logger.info(f"nlm_ingest_pdf() - split doc {doc_id} for user {user_id}")

doc = Document.objects.get(pk=doc_id)
Expand Down
52 changes: 46 additions & 6 deletions opencontractserver/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import logging
from unittest.mock import Mock, patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.test import Client, TestCase
from django.urls import reverse

from opencontractserver.corpuses.admin import CorpusAdmin
from opencontractserver.corpuses.models import Corpus

User = get_user_model()

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -81,12 +85,13 @@ def test_view_user(self):
class TestAnalyzerAdmin(TestCase):
def setUp(self) -> None:

User.objects.create_superuser(
self.user = User.objects.create_superuser(
username="superuser", password="secret", email="admin@example.com"
)

self.admin_client = Client()
self.admin_client.login(username="superuser", password="secret")
self.corpus_admin = CorpusAdmin(Corpus, None)

def test_gremlin_changelist(self):
url = reverse("admin:analyzer_gremlinengine_changelist")
Expand All @@ -110,8 +115,43 @@ def test_gremlin_add(self):

logger.info(f"test_add - response: {response}")

# def test_view_gremlin(self, admin_client):
# user = User.objects.get(username="admin")
# url = reverse("admin:analyzer_gremlinengine_change", kwargs={"object_id": user.pk})
# response = admin_client.get(url)
# assert response.status_code == 200
def test_display_icon_with_icon(self):
obj = Mock(icon=Mock(url="http://example.com/icon.png"))
result = self.corpus_admin.display_icon(obj)
self.assertIn('src="http://example.com/icon.png"', result)
self.assertIn('width="50"', result)
self.assertIn('height="50"', result)

def test_display_icon_without_icon(self):
obj = Mock(icon=None)
result = self.corpus_admin.display_icon(obj)
self.assertEqual(result, "No icon")

@patch("opencontractserver.tasks.make_corpus_public_task.si")
def test_make_public(self, mock_task):
mock_task.return_value.apply_async.return_value = None

corpus1 = Corpus(
title="Test", description="Some important stuff!", creator=self.user
)
corpus1.save()

corpus2 = Corpus(
title="Test2", description="Some important stuff!", creator=self.user
)
corpus2.save()

request = Mock()
self.corpus_admin.message_user = Mock()

self.corpus_admin.make_public(request, Corpus.objects.all())

# Verify make_corpus_public_task was called for each corpus
mock_task.assert_any_call(corpus_id=1)
mock_task.assert_any_call(corpus_id=2)
self.assertEqual(mock_task.call_count, 2)

# Verify the correct message was sent to the user
self.corpus_admin.message_user.assert_called_once_with(
request, "Started making 2 corpus(es) public."
)