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

Initial concept tree endpoint #7

Merged
merged 9 commits into from
May 21, 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
71 changes: 71 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CI

on:
# push: -- just run on PRs for now
pull_request:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgis/postgis:13-3.0
env:
POSTGRES_PASSWORD: postgis
POSTGRES_DB: arches
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- name: Check out arches
uses: actions/checkout@v3
with:
repository: archesproject/arches
ref: dev/7.6.x
path: arches

- name: Check out arches_rdm
uses: actions/checkout@v2
with:
path: arches_rdm

- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install arches_rdm
working-directory: arches_rdm
run: |
python -m pip install --upgrade pip
pip install .
echo Project installed

- name: Install arches dev version
working-directory: arches
run: |
python -m pip uninstall arches -y
python -m pip install .
echo Arches dev version installed

- name: Install Java, GDAL, and other system dependencies
run: |
sudo apt update
sudo apt-get install libxml2-dev libpq-dev openjdk-8-jdk libgdal-dev
echo Postgres and ES dependencies installed

- uses: ankane/setup-elasticsearch@v1
with:
elasticsearch-version: 8

- name: Check for missing migrations
working-directory: arches_rdm
run: |
PYTHONPATH="../arches" python manage.py makemigrations --check --settings=arches_rdm.test_settings

- name: Run arches_rdm unit tests
working-directory: arches_rdm
run: |
PYTHONPATH="../arches" python -W default::DeprecationWarning manage.py test --settings=arches_rdm.test_settings
Empty file removed __init__.py
Empty file.
23 changes: 23 additions & 0 deletions arches_rdm/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CONCEPTS_GRAPH_ID = "bf73e576-4888-11ee-8a8d-11afefc4bff7"
SCHEMES_GRAPH_ID = "56788995-423b-11ee-8a8d-11afefc4bff7"

TOP_CONCEPT_OF_NODE_AND_NODEGROUP = "bf73e5b9-4888-11ee-8a8d-11afefc4bff7"

BROADER_NODE_AND_NODEGROUP = "bf73e5f5-4888-11ee-8a8d-11afefc4bff7"

CONCEPT_NAME_NODEGROUP = "bf73e616-4888-11ee-8a8d-11afefc4bff7"
CONCEPT_NAME_CONTENT_NODE = "bf73e695-4888-11ee-8a8d-11afefc4bff7"
CONCEPT_NAME_TYPE_NODE = "b08eebb4-d44c-11ee-a986-0242ac130005"
CONCEPT_NAME_LANGUAGE_NODE = "444b7de6-d44c-11ee-8fe3-0242ac130005"

SCHEME_NAME_NODEGROUP = "749a27cf-423c-11ee-8a8d-11afefc4bff7"
SCHEME_NAME_CONTENT_NODE = "749a27d5-423c-11ee-8a8d-11afefc4bff7"
SCHEME_NAME_TYPE_NODE = "1330cc4c-d44d-11ee-9261-0242ac130005"
SCHEME_NAME_LANGUAGE_NODE = "2deaf45e-d44d-11ee-b78d-0242ac130005"

PREF_LABEL_VALUE_ID = "3b8a03f1-9047-48e4-9ca0-b3fe887f6f9d"
ALT_LABEL_VALUE_ID = "c02f97c5-da16-4ff0-864a-92c34da84e38"

# Old RDM concepts, values
LANGUAGE_CONCEPT_ID = "845cc417-ef77-4582-9271-ffba5e4cabc9"
ENGLISH_VALUE_ID = "de978fd0-2819-4855-8858-8c089780f32c"
34 changes: 34 additions & 0 deletions arches_rdm/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os

# Depends on /arches being on the python path, as arches.tests is not importable
from tests.test_settings import *

APP_NAME = "arches_rdm"
APP_ROOT = os.path.dirname(__file__)
INSTALLED_APPS = (
"webpack_loader",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.gis",
"arches",
"arches.app.models",
"arches.management",
"guardian",
"captcha",
"revproxy",
"corsheaders",
"oauth2_provider",
"django_celery_results",
"arches_rdm",
)
ROOT_URLCONF = "arches_rdm.urls"

LOCALE_PATHS = [os.path.join(APP_ROOT, "locale")]

# Further settings may need to be added from project, just don't
# want to clobber anything from core test settings for now.
# Also, settings can be overridden directly. See @override_settings
214 changes: 214 additions & 0 deletions arches_rdm/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import json

from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
from django.urls import reverse

from arches.app.models.models import (
Concept,
GraphModel,
Node,
NodeGroup,
ResourceInstance,
TileModel,
Value,
)

from arches_rdm.const import (
ENGLISH_VALUE_ID,
CONCEPTS_GRAPH_ID,
LANGUAGE_CONCEPT_ID,
SCHEMES_GRAPH_ID,
TOP_CONCEPT_OF_NODE_AND_NODEGROUP,
BROADER_NODE_AND_NODEGROUP,
CONCEPT_NAME_NODEGROUP,
CONCEPT_NAME_CONTENT_NODE,
CONCEPT_NAME_LANGUAGE_NODE,
CONCEPT_NAME_TYPE_NODE,
SCHEME_NAME_NODEGROUP,
SCHEME_NAME_CONTENT_NODE,
SCHEME_NAME_LANGUAGE_NODE,
SCHEME_NAME_TYPE_NODE,
PREF_LABEL_VALUE_ID,
)


def setUpModule():
"""Bootstrap just a few nodes as an alternative to loading the entire package."""
if not GraphModel.objects.filter(pk=SCHEMES_GRAPH_ID).exists():
GraphModel.objects.create(pk=SCHEMES_GRAPH_ID, isresource=True)
GraphModel.objects.create(pk=CONCEPTS_GRAPH_ID, isresource=True)

for nodegroup_id, node_id, node_name, datatype in [
(
TOP_CONCEPT_OF_NODE_AND_NODEGROUP,
TOP_CONCEPT_OF_NODE_AND_NODEGROUP,
"Top concept of",
"concept-list",
),
(
BROADER_NODE_AND_NODEGROUP,
BROADER_NODE_AND_NODEGROUP,
"Broader concept",
"concept-list",
),
(
SCHEME_NAME_NODEGROUP,
SCHEME_NAME_CONTENT_NODE,
"Name content",
"concept-list",
),
(
SCHEME_NAME_NODEGROUP,
SCHEME_NAME_LANGUAGE_NODE,
"Name language",
"string",
),
(
CONCEPT_NAME_NODEGROUP,
CONCEPT_NAME_CONTENT_NODE,
"Name content",
"concept-list",
),
(
CONCEPT_NAME_NODEGROUP,
CONCEPT_NAME_LANGUAGE_NODE,
"Name language",
"string",
),
]:
NodeGroup.objects.get_or_create(pk=nodegroup_id)
Node.objects.create(
pk=node_id,
graph_id=CONCEPTS_GRAPH_ID,
nodegroup_id=nodegroup_id,
name=node_name,
istopnode=False,
datatype=datatype,
isrequired=datatype == "string",
)

Concept.objects.get_or_create(
conceptid=LANGUAGE_CONCEPT_ID,
nodetype_id="Concept",
)
Value.objects.get_or_create(
concept_id=LANGUAGE_CONCEPT_ID,
valueid=ENGLISH_VALUE_ID,
valuetype_id="prefLabel",
value="en-US",
)


def localized_string(text, language="en", direction="ltr"):
return {language: {"value": text, "direction": direction}}


class ConceptTreeViewTests(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.client = Client()
cls.admin = User.objects.get(username="admin")

@classmethod
def setUpTestData(cls):
# Create a scheme with five concepts, each one narrower than the last,
# and each concept after the top concept also narrower than the top.
cls.scheme = ResourceInstance.objects.create(graph_id=SCHEMES_GRAPH_ID)
TileModel.objects.create(
resourceinstance=cls.scheme,
nodegroup_id=SCHEME_NAME_NODEGROUP,
data={
SCHEME_NAME_CONTENT_NODE: localized_string("Test Scheme"),
SCHEME_NAME_TYPE_NODE: [PREF_LABEL_VALUE_ID],
SCHEME_NAME_LANGUAGE_NODE: [ENGLISH_VALUE_ID],
},
)

MAX_DEPTH = 5
CONCEPT_COUNT = 5
cls.concepts = [
ResourceInstance(graph_id=SCHEMES_GRAPH_ID) for _ in range(CONCEPT_COUNT)
]
ResourceInstance.objects.bulk_create(cls.concepts)

for i, concept in enumerate(cls.concepts):
# Create label tile
TileModel.objects.create(
resourceinstance=concept,
nodegroup_id=CONCEPT_NAME_NODEGROUP,
data={
CONCEPT_NAME_CONTENT_NODE: localized_string(f"Concept {i + 1}"),
CONCEPT_NAME_TYPE_NODE: [PREF_LABEL_VALUE_ID],
CONCEPT_NAME_LANGUAGE_NODE: [ENGLISH_VALUE_ID],
},
)
# Create top concept/narrower tile
if i == 0:
TileModel.objects.create(
resourceinstance=concept,
nodegroup_id=TOP_CONCEPT_OF_NODE_AND_NODEGROUP,
data={
TOP_CONCEPT_OF_NODE_AND_NODEGROUP: [
{"resourceId": str(cls.scheme.pk)},
],
},
)
elif i < MAX_DEPTH:
TileModel.objects.create(
resourceinstance=concept,
nodegroup_id=BROADER_NODE_AND_NODEGROUP,
data={
BROADER_NODE_AND_NODEGROUP: [
# Previous concept
{"resourceId": str(cls.concepts[i - 1].pk)},
# Also add top concept
{"resourceId": str(cls.concepts[0].pk)},
],
},
)
else:
TileModel.objects.create(
resourceinstance=concept,
nodegroup_id=BROADER_NODE_AND_NODEGROUP,
data={
BROADER_NODE_AND_NODEGROUP: [
# Top concept only
{"resourceId": str(cls.concepts[0].pk)},
],
},
)

def test_get_concept_trees(self):
self.client.force_login(self.admin)
with self.assertNumQueries(6):
# 1: session
# 2: auth
# 3: select languages, subquery for concept values
# 4: select broader tiles, subquery for labels
# 5: select top concept tiles, subquery for labels
# 6: select schemes, subquery for labels
response = self.client.get(reverse("concept_trees"))

self.assertEqual(response.status_code, 200)
result = json.loads(response.content)
scheme = result["schemes"][0]

self.assertEqual(scheme["labels"][0]["value"], "Test Scheme")
self.assertEqual(len(scheme["top_concepts"]), 1)
top = scheme["top_concepts"][0]
self.assertEqual(top["labels"][0]["value"], "Concept 1")
self.assertEqual(len(top["narrower"]), 4)
self.assertEqual(
{n["labels"][0]["value"] for n in top["narrower"]},
{"Concept 2", "Concept 3", "Concept 4", "Concept 5"},
)
concept_2 = [
c for c in top["narrower"] if c["labels"][0]["value"] == "Concept 2"
][0]
self.assertEqual(
{n["labels"][0]["value"] for n in concept_2["narrower"]},
{"Concept 3"},
)
8 changes: 6 additions & 2 deletions arches_rdm/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path
from django.urls import include, path

from arches_rdm.views import ConceptTreeView

urlpatterns = []
urlpatterns = [
path("", include("arches.urls")),
path("concept_trees/", ConceptTreeView.as_view(), name="concept_trees"),
]
Loading
Loading