Skip to content

Commit 669def4

Browse files
JWittmeyerJohannes Hötter
andauthored
changes for admin dashbaord extention (#43)
* Changes submodule url to ssh * Adds alembic file for comments & user/org management * submodule update & new query * Adds get comments query * Adds new endpoints * Adds new endpoints * enable adding manual rlas with source id * Adds logic to remove annotator labels * Adds logic to collect first heuristic * adds stats and states to crowd labeling * adds confidence to manually labeled data * crowd labeling for ner * adds update progress on payload * adds role mgmt to the api * Adds lock option and removes unused table * Adds link refresh on outdated data * Changes faulty alembic version * Adds comment preparations * Submodule change and role management * Merge issue resolve * Adds notification time for link chnages after the commit * Import/Export of projects addition * resolves pr comments * Submodule change Co-authored-by: Johannes Hötter <[email protected]>
1 parent 499ad78 commit 669def4

File tree

33 files changed

+1233
-55
lines changed

33 files changed

+1233
-55
lines changed

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[submodule "submodules/model"]
22
path = submodules/model
3-
url = https://github.com/code-kern-ai/refinery-submodule-model.git
3+
url = git@github.com:code-kern-ai/refinery-submodule-model.git
44
[submodule "submodules/s3"]
55
path = submodules/s3
6-
url = https://github.com/code-kern-ai/refinery-submodule-s3.git
6+
url = git@github.com:code-kern-ai/refinery-submodule-s3.git
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""Adds comment & link tables & some org management
2+
3+
Revision ID: 9618924f9679
4+
Revises: 5b3a4deea1c4
5+
Create Date: 2022-09-15 07:15:02.934928
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "9618924f9679"
14+
down_revision = "5b3a4deea1c4"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table(
22+
"comment",
23+
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
24+
sa.Column("project_id", postgresql.UUID(as_uuid=True), nullable=True),
25+
sa.Column("xfkey", postgresql.UUID(as_uuid=True), nullable=True),
26+
sa.Column("xftype", sa.String(), nullable=True),
27+
sa.Column("order_key", sa.Integer(), autoincrement=True, nullable=True),
28+
sa.Column("comment", sa.String(), nullable=True),
29+
sa.Column("is_markdown", sa.Boolean(), nullable=True),
30+
sa.Column("is_private", sa.Boolean(), nullable=True),
31+
sa.Column("created_by", postgresql.UUID(as_uuid=True), nullable=True),
32+
sa.Column("created_at", sa.DateTime(), nullable=True),
33+
sa.ForeignKeyConstraint(
34+
["created_by"],
35+
["user.id"],
36+
),
37+
sa.ForeignKeyConstraint(["project_id"], ["project.id"], ondelete="CASCADE"),
38+
sa.PrimaryKeyConstraint("id"),
39+
)
40+
op.create_index(
41+
op.f("ix_comment_created_by"), "comment", ["created_by"], unique=False
42+
)
43+
op.create_index(
44+
op.f("ix_comment_project_id"), "comment", ["project_id"], unique=False
45+
)
46+
op.create_index(op.f("ix_comment_xfkey"), "comment", ["xfkey"], unique=False)
47+
op.create_index(op.f("ix_comment_xftype"), "comment", ["xftype"], unique=False)
48+
op.create_table(
49+
"labeling_access_link",
50+
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
51+
sa.Column("project_id", postgresql.UUID(as_uuid=True), nullable=True),
52+
sa.Column("link", sa.String(), nullable=True),
53+
sa.Column("data_slice_id", postgresql.UUID(as_uuid=True), nullable=True),
54+
sa.Column("heuristic_id", postgresql.UUID(as_uuid=True), nullable=True),
55+
sa.Column("link_type", sa.String(), nullable=True),
56+
sa.Column("created_at", sa.DateTime(), nullable=True),
57+
sa.Column("created_by", postgresql.UUID(as_uuid=True), nullable=True),
58+
sa.Column("is_locked", sa.Boolean(), nullable=True),
59+
sa.Column("changed_at", sa.DateTime(), nullable=True),
60+
sa.ForeignKeyConstraint(["project_id"], ["project.id"], ondelete="CASCADE"),
61+
sa.ForeignKeyConstraint(["created_by"], ["user.id"], ondelete="CASCADE"),
62+
sa.ForeignKeyConstraint(
63+
["data_slice_id"], ["data_slice.id"], ondelete="CASCADE"
64+
),
65+
sa.ForeignKeyConstraint(
66+
["heuristic_id"], ["information_source.id"], ondelete="CASCADE"
67+
),
68+
sa.PrimaryKeyConstraint("id"),
69+
)
70+
op.create_index(
71+
op.f("ix_labeling_access_link_project_id"),
72+
"labeling_access_link",
73+
["project_id"],
74+
unique=False,
75+
)
76+
op.create_index(
77+
op.f("ix_labeling_access_link_created_by"),
78+
"labeling_access_link",
79+
["created_by"],
80+
unique=False,
81+
)
82+
op.create_index(
83+
op.f("ix_labeling_access_link_data_slice_id"),
84+
"labeling_access_link",
85+
["data_slice_id"],
86+
unique=False,
87+
)
88+
op.create_index(
89+
op.f("ix_labeling_access_link_heuristic_id"),
90+
"labeling_access_link",
91+
["heuristic_id"],
92+
unique=False,
93+
)
94+
op.add_column("organization", sa.Column("started_at", sa.DateTime(), nullable=True))
95+
op.add_column("organization", sa.Column("is_paying", sa.Boolean(), nullable=True))
96+
op.add_column("organization", sa.Column("created_at", sa.DateTime(), nullable=True))
97+
op.add_column("user", sa.Column("role", sa.String(), nullable=True))
98+
# ### end Alembic commands ###
99+
100+
101+
def downgrade():
102+
# ### commands auto generated by Alembic - please adjust! ###
103+
op.drop_column("user", "role")
104+
op.drop_column("organization", "created_at")
105+
op.drop_column("organization", "is_paying")
106+
op.drop_column("organization", "started_at")
107+
op.drop_index(
108+
op.f("ix_labeling_access_link_project_id"), table_name="labeling_access_link"
109+
)
110+
op.drop_index(
111+
op.f("ix_labeling_access_link_heuristic_id"), table_name="labeling_access_link"
112+
)
113+
op.drop_index(
114+
op.f("ix_labeling_access_link_data_slice_id"), table_name="labeling_access_link"
115+
)
116+
op.drop_index(
117+
op.f("ix_labeling_access_link_created_by"), table_name="labeling_access_link"
118+
)
119+
op.drop_table("labeling_access_link")
120+
op.drop_index(op.f("ix_comment_xftype"), table_name="comment")
121+
op.drop_index(op.f("ix_comment_xfkey"), table_name="comment")
122+
op.drop_index(op.f("ix_comment_project_id"), table_name="comment")
123+
op.drop_index(op.f("ix_comment_created_by"), table_name="comment")
124+
op.drop_table("comment")
125+
# ### end Alembic commands ###

api/project.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from controller.auth import manager as auth_manager
66
from controller.project import manager as project_manager
77
from controller.attribute import manager as attribute_manager
8+
from submodules.model import exceptions
89

910

1011
logging.basicConfig(level=logging.DEBUG)
@@ -14,7 +15,14 @@ class ProjectDetails(HTTPEndpoint):
1415
def get(self, request) -> JSONResponse:
1516
project_id = request.path_params["project_id"]
1617
user_id = request.query_params["user_id"]
17-
auth_manager.check_project_access_from_user_id(user_id, project_id)
18+
try:
19+
auth_manager.check_project_access_from_user_id(
20+
user_id, project_id, from_api=True
21+
)
22+
except exceptions.EntityNotFoundException:
23+
return JSONResponse({"error": "Could not find project"}, status_code=404)
24+
except exceptions.AccessDeniedException:
25+
return JSONResponse({"error": "Access denied"}, status_code=403)
1826
project = project_manager.get_project(project_id)
1927
attributes = attribute_manager.get_all_attributes(project_id)
2028
result = {

api/transfer.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from controller.transfer import association_transfer_manager
1515
from controller.auth import manager as auth
1616

17-
from submodules.model import enums
17+
from submodules.model import enums, exceptions
1818
from util.notification import create_notification
1919
from submodules.model.enums import NotificationType
2020
from submodules.model.models import UploadTask
@@ -63,7 +63,14 @@ def get(self, request) -> JSONResponse:
6363
project_id = request.path_params["project_id"]
6464
user_id = request.query_params["user_id"]
6565
num_samples = request.query_params.get("num_samples")
66-
auth_manager.check_project_access_from_user_id(user_id, project_id)
66+
try:
67+
auth_manager.check_project_access_from_user_id(
68+
user_id, project_id, from_api=True
69+
)
70+
except exceptions.EntityNotFoundException:
71+
return JSONResponse({"error": "Could not find project"}, status_code=404)
72+
except exceptions.AccessDeniedException:
73+
return JSONResponse({"error": "Access denied"}, status_code=403)
6774
result = transfer_manager.export_records(project_id, num_samples)
6875
return JSONResponse(result)
6976

@@ -73,7 +80,14 @@ def get(self, request) -> JSONResponse:
7380
project_id = request.path_params["project_id"]
7481
list_id = request.path_params["knowledge_base_id"]
7582
user_id = request.query_params["user_id"]
76-
auth_manager.check_project_access_from_user_id(user_id, project_id)
83+
try:
84+
auth_manager.check_project_access_from_user_id(
85+
user_id, project_id, from_api=True
86+
)
87+
except exceptions.EntityNotFoundException:
88+
return JSONResponse({"error": "Could not find project"}, status_code=404)
89+
except exceptions.AccessDeniedException:
90+
return JSONResponse({"error": "Access denied"}, status_code=403)
7791
result = transfer_manager.export_knowledge_base(project_id, list_id)
7892
return JSONResponse(result)
7993

@@ -85,7 +99,14 @@ async def post(self, request) -> JSONResponse:
8599
request_body = await request.json()
86100

87101
user_id = request_body["user_id"]
88-
auth_manager.check_project_access_from_user_id(user_id, project_id)
102+
try:
103+
auth_manager.check_project_access_from_user_id(
104+
user_id, project_id, from_api=True
105+
)
106+
except exceptions.EntityNotFoundException:
107+
return JSONResponse({"error": "Could not find project"}, status_code=404)
108+
except exceptions.AccessDeniedException:
109+
return JSONResponse({"error": "Access denied"}, status_code=403)
89110
file_name = request_body["file_name"]
90111
file_type = request_body["file_type"]
91112
file_import_options = request_body.get("file_import_options")
@@ -121,7 +142,14 @@ async def post(self, request) -> JSONResponse:
121142
project_id = request.path_params["project_id"]
122143
request_body = await request.json()
123144
user_id = request_body["user_id"]
124-
auth_manager.check_project_access_from_user_id(user_id, project_id)
145+
try:
146+
auth_manager.check_project_access_from_user_id(
147+
user_id, project_id, from_api=True
148+
)
149+
except exceptions.EntityNotFoundException:
150+
return JSONResponse({"error": "Could not find project"}, status_code=404)
151+
except exceptions.AccessDeniedException:
152+
return JSONResponse({"error": "Access denied"}, status_code=403)
125153
new_associations_added = association_transfer_manager.import_associations(
126154
project_id,
127155
user_id,
@@ -140,7 +168,14 @@ def get(self, request) -> JSONResponse:
140168
project_id = request.path_params["project_id"]
141169
task_id = request.path_params["task_id"]
142170
user_id = request.query_params["user_id"]
143-
auth_manager.check_project_access_from_user_id(user_id, project_id)
171+
try:
172+
auth_manager.check_project_access_from_user_id(
173+
user_id, project_id, from_api=True
174+
)
175+
except exceptions.EntityNotFoundException:
176+
return JSONResponse({"error": "Could not find project"}, status_code=404)
177+
except exceptions.AccessDeniedException:
178+
return JSONResponse({"error": "Access denied"}, status_code=403)
144179
task = upload_task_manager.get_upload_task(project_id, task_id)
145180
task_dict = {
146181
"id": str(task.id),

controller/auth/manager.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from controller.project import manager as project_manager
99
from controller.user import manager as user_manager
1010
from controller.organization import manager as organization_manager
11+
from submodules.model import enums, exceptions
1112
from submodules.model.models import Organization, Project, User
1213
from controller.misc import manager as misc_manager
14+
import sqlalchemy
1315

1416

1517
def get_organization_id_by_info(info) -> Organization:
@@ -62,13 +64,22 @@ def check_admin_access(info) -> None:
6264
raise GraphQLError("Admin access required")
6365

6466

65-
def check_project_access_from_user_id(user_id: str, project_id: str) -> bool:
67+
def check_project_access_from_user_id(
68+
user_id: str, project_id: str, from_api: bool = False
69+
) -> bool:
6670
organization_id: str = get_organization_by_user_id(user_id).id
67-
project: Project = project_manager.get_project_with_orga_id(
68-
organization_id, project_id
69-
)
71+
try:
72+
project: Project = project_manager.get_project_with_orga_id(
73+
organization_id, project_id
74+
)
75+
except sqlalchemy.exc.DataError:
76+
raise exceptions.EntityNotFoundException("Project not found")
7077
if project is None:
71-
raise GraphQLError("Project not found")
78+
raise exceptions.EntityNotFoundException("Project not found")
79+
if from_api:
80+
user = user_manager.get_user(user_id)
81+
if user.role != enums.UserRoles.ENGINEER.value:
82+
raise exceptions.AccessDeniedException("Access denied")
7283
return True
7384

7485

controller/comment/__init__.py

Whitespace-only changes.

controller/comment/manager.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from typing import Any, Dict, List, Optional, Union
2+
from submodules.model import enums
3+
from submodules.model.models import Comment, User
4+
5+
from submodules.model.business_objects import comments
6+
7+
8+
def has_comments(
9+
xftype: str,
10+
xfkey: Optional[str] = None,
11+
project_id: Optional[str] = None,
12+
group_by_xfkey: bool = False,
13+
) -> Union[bool, Dict[str, bool]]:
14+
15+
try:
16+
xftype_parsed = enums.CommentCategory[xftype.upper()]
17+
except KeyError:
18+
raise ValueError(f"Invalid comment category: {xftype}")
19+
20+
return comments.has_comments(xftype_parsed, xfkey, project_id, group_by_xfkey)
21+
22+
23+
def get_comments(
24+
xftype: str,
25+
user_id: str,
26+
xfkey: Optional[str] = None,
27+
project_id: Optional[str] = None,
28+
) -> str:
29+
30+
try:
31+
xftype_parsed = enums.CommentCategory[xftype.upper()]
32+
except KeyError:
33+
raise ValueError(f"Invalid comment category: {xftype}")
34+
35+
return comments.get_by_all_by_category(
36+
xftype_parsed, user_id, xfkey, project_id, True
37+
)
38+
39+
40+
def create_comment(
41+
xfkey: str,
42+
xftype: str,
43+
comment: str,
44+
user_id: str,
45+
project_id: Optional[str] = None,
46+
is_private: Optional[bool] = None,
47+
) -> Comment:
48+
try:
49+
xftype = enums.CommentCategory[xftype.upper()].value
50+
except KeyError:
51+
raise ValueError(f"Invalid comment type: {xftype}")
52+
comments.create(
53+
xfkey,
54+
xftype,
55+
comment,
56+
user_id,
57+
project_id,
58+
None,
59+
None,
60+
is_private,
61+
None,
62+
with_commit=True,
63+
)
64+
65+
66+
def update_comment(
67+
comment_id: str,
68+
user: User,
69+
changes: Dict[str, Any],
70+
) -> Comment:
71+
item = comments.get(comment_id)
72+
73+
if not item:
74+
raise ValueError(f"Can't find comment")
75+
76+
if user.role != enums.UserRoles.ENGINEER.value and user.id != item.created_by:
77+
raise ValueError(f"Can't update comment")
78+
comments.change(item, changes, with_commit=True)

0 commit comments

Comments
 (0)