Skip to content

Commit 4f4d8b7

Browse files
Admin features new (#119)
* Adds admin features * Updates submodule * Adds archiving mechanism for admin query * Adds limits to orga and changes mutation to change them * Updates submodule * Resolves PR comments * Adds task types enums * Removes type based task cancel mutations * Adds limits * Resolves PR comments * Resolves PR comments * Small fix * Updates submodule * Updates submodule * Small change * Resolves PR comments * Updates submodule * Updates submodule * Merge submodules --------- Co-authored-by: Lina <[email protected]>
1 parent 591c0fb commit 4f4d8b7

File tree

15 files changed

+419
-12
lines changed

15 files changed

+419
-12
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Adds limits to orga
2+
3+
Revision ID: 3b118e1e02cb
4+
Revises: a5c341d1a7ef
5+
Create Date: 2023-03-01 10:49:53.348290
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '3b118e1e02cb'
14+
down_revision = 'a5c341d1a7ef'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column('organization', sa.Column('max_rows', sa.Integer(), nullable=True))
22+
op.add_column('organization', sa.Column('max_cols', sa.Integer(), nullable=True))
23+
op.add_column('organization', sa.Column('max_char_count', sa.Integer(), nullable=True))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade():
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column('organization', 'max_char_count')
30+
op.drop_column('organization', 'max_cols')
31+
op.drop_column('organization', 'max_rows')
32+
# ### end Alembic commands ###
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Adds admin features
2+
3+
Revision ID: a5c341d1a7ef
4+
Revises: 0e0e4aeac7eb
5+
Create Date: 2023-02-22 08:43:54.162780
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 = 'a5c341d1a7ef'
14+
down_revision = '0e0e4aeac7eb'
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('admin_message',
22+
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
23+
sa.Column('text', sa.String(), nullable=True),
24+
sa.Column('level', sa.String(), nullable=True),
25+
sa.Column('archived', sa.Boolean(), nullable=True),
26+
sa.Column('created_at', sa.DateTime(), nullable=True),
27+
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
28+
sa.Column('archive_date', sa.DateTime(), nullable=True),
29+
sa.Column('archived_by', postgresql.UUID(as_uuid=True), nullable=True),
30+
sa.Column('archived_reason', sa.String(), nullable=True),
31+
sa.ForeignKeyConstraint(['archived_by'], ['user.id'], ),
32+
sa.ForeignKeyConstraint(['created_by'], ['user.id'], ),
33+
sa.PrimaryKeyConstraint('id')
34+
)
35+
op.create_index(op.f('ix_admin_message_archived_by'), 'admin_message', ['archived_by'], unique=False)
36+
op.create_index(op.f('ix_admin_message_created_by'), 'admin_message', ['created_by'], unique=False)
37+
op.add_column('user', sa.Column('last_interaction', sa.DateTime(), nullable=True))
38+
# ### end Alembic commands ###
39+
40+
41+
def downgrade():
42+
# ### commands auto generated by Alembic - please adjust! ###
43+
op.drop_column('user', 'last_interaction')
44+
op.drop_index(op.f('ix_admin_message_created_by'), table_name='admin_message')
45+
op.drop_index(op.f('ix_admin_message_archived_by'), table_name='admin_message')
46+
op.drop_table('admin_message')
47+
# ### end Alembic commands ###
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import List
2+
from datetime import datetime
3+
from submodules.model.business_objects import admin_message, general
4+
from submodules.model.models import AdminMessage
5+
from util.notification import send_global_update_for_all_organizations
6+
7+
8+
def get_messages(limit: int = 100, active_only: bool = True) -> List[AdminMessage]:
9+
messages = admin_message.get_all_active(limit)
10+
message_archived = False
11+
messages_to_return = []
12+
now = datetime.now()
13+
for message in messages:
14+
if now > message.archive_date:
15+
admin_message.archive(
16+
message.id, None, now, "Archive date reached.", with_commit=False
17+
)
18+
message_archived = True
19+
elif active_only:
20+
messages_to_return.append(message)
21+
22+
if message_archived:
23+
general.commit()
24+
send_global_update_for_all_organizations("admin_message")
25+
26+
if not active_only:
27+
messages_to_return = admin_message.get_all(limit)
28+
return messages_to_return
29+
30+
31+
def create_admin_message(
32+
text: str, level: str, archive_date: datetime, created_by: str
33+
) -> AdminMessage:
34+
now = datetime.now().astimezone(archive_date.tzinfo)
35+
36+
if archive_date < now:
37+
raise ValueError("Archive date not valid")
38+
return admin_message.create(
39+
text=text,
40+
level=level,
41+
archive_date=archive_date,
42+
created_by=created_by,
43+
with_commit=True,
44+
)
45+
46+
47+
def archive_admin_message(
48+
message_id: str, archived_by: str, archived_reason: str
49+
) -> None:
50+
archive_date = datetime.now()
51+
admin_message.archive(
52+
message_id, archived_by, archive_date, archived_reason, with_commit=True
53+
)

controller/monitor/manager.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from typing import Any, List
2+
from submodules.model.business_objects import monitor as task_monitor
3+
4+
5+
def monitor_all_tasks(project_id: str = None, only_running: bool = True) -> List[Any]:
6+
return task_monitor.get_all_tasks(project_id, only_running)
7+
8+
9+
def cancel_all_running_tasks(project_id: str = None) -> None:
10+
task_monitor.cancel_all_running_tasks(project_id)
11+
12+
13+
def cancel_upload_task(project_id: str = None, upload_task_id: str = None) -> None:
14+
task_monitor.set_upload_task_to_failed(project_id, upload_task_id, with_commit=True)
15+
16+
17+
def cancel_weak_supervision(project_id: str = None, payload_id: str = None) -> None:
18+
task_monitor.set_weak_supervision_to_failed(
19+
project_id, payload_id, with_commit=True
20+
)
21+
22+
23+
def cancel_attribute_calculation(
24+
project_id: str = None, attribute_id: str = None
25+
) -> None:
26+
task_monitor.set_attribute_calculation_to_failed(
27+
project_id, attribute_id, with_commit=True
28+
)
29+
30+
31+
def cancel_embedding(project_id: str = None, embedding_id: str = None) -> None:
32+
task_monitor.set_embedding_to_failed(project_id, embedding_id, with_commit=True)
33+
34+
35+
def cancel_information_source_payload(
36+
project_id: str = None, payload_id: str = None
37+
) -> None:
38+
task_monitor.set_information_source_payloads_to_failed(
39+
project_id, payload_id, with_commit=True
40+
)
41+
42+
43+
def cancel_record_tokenization_task(
44+
project_id: str = None,
45+
tokenization_task_id: str = None,
46+
) -> None:
47+
task_monitor.set_record_tokenization_task_to_failed(
48+
project_id, tokenization_task_id, with_commit=True
49+
)

controller/user/manager.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
from typing import Dict
22
from submodules.model import User, enums
3-
from submodules.model.business_objects import user
4-
from submodules.model.business_objects import general
3+
from submodules.model.business_objects import user, user_activity, general
54
from controller.auth import kratos
65
from submodules.model.exceptions import EntityNotFoundException
76
from controller.organization import manager as organization_manager
7+
from datetime import datetime, timedelta
8+
from util.decorator import param_throttle
89

910

1011
def get_user(user_id: str) -> User:
11-
return user.get(user_id)
12+
user_item = user.get(user_id)
13+
if user_item:
14+
update_last_interaction(user_item.id)
15+
return user_item
1216

1317

1418
def get_or_create_user(user_id: str) -> User:
1519
user_item = user.get(user_id)
1620
if not user_item:
1721
user_item = user.create(user_id, with_commit=True)
22+
update_last_interaction(user_item.id)
1823
return user_item
1924

2025

@@ -74,3 +79,16 @@ def remove_organization_from_user(user_mail: str) -> None:
7479
raise Exception("User has no organization")
7580

7681
user.remove_organization(user_id, with_commit=True)
82+
83+
84+
def get_active_users(minutes: str, order_by_interaction: bool) -> User:
85+
now = datetime.now()
86+
last_interaction_range = (now - timedelta(minutes=minutes)) if minutes else None
87+
return user_activity.get_active_users_in_range(
88+
last_interaction_range, order_by_interaction
89+
)
90+
91+
92+
@param_throttle(seconds=10)
93+
def update_last_interaction(user_id: str) -> None:
94+
user_activity.update_last_interaction(user_id)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from datetime import datetime
2+
from controller.auth import manager as auth
3+
from controller.admin_message import manager
4+
import graphene
5+
from util.notification import send_global_update_for_all_organizations
6+
7+
8+
class CreateAdminMessage(graphene.Mutation):
9+
class Arguments:
10+
text = graphene.String(required=True)
11+
level = graphene.String(required=True)
12+
archive_date = graphene.DateTime(required=True)
13+
14+
ok = graphene.Boolean()
15+
16+
def mutate(self, info, text: str, level: str, archive_date: datetime):
17+
auth.check_demo_access(info)
18+
auth.check_admin_access(info)
19+
user_id = auth.get_user_id_by_info(info)
20+
manager.create_admin_message(text, level, archive_date, user_id)
21+
send_global_update_for_all_organizations("admin_message")
22+
return CreateAdminMessage(ok=True)
23+
24+
25+
class ArchiveAdminMessage(graphene.Mutation):
26+
class Arguments:
27+
message_id = graphene.ID(required=True)
28+
archived_reason = graphene.String(required=True)
29+
30+
ok = graphene.Boolean()
31+
32+
def mutate(self, info, message_id: str, archived_reason: str):
33+
auth.check_demo_access(info)
34+
auth.check_admin_access(info)
35+
user_id = auth.get_user_id_by_info(info)
36+
manager.archive_admin_message(message_id, user_id, archived_reason)
37+
send_global_update_for_all_organizations(f"admin_message")
38+
return ArchiveAdminMessage(ok=True)
39+
40+
41+
class AdminMessageMutation(graphene.ObjectType):
42+
create_admin_message = CreateAdminMessage.Field()
43+
archive_admin_message = ArchiveAdminMessage.Field()

graphql_api/mutation/monitor.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import graphene
2+
from controller.auth import manager as auth
3+
from controller.monitor import manager
4+
from submodules.model import enums
5+
6+
7+
class CancelTask(graphene.Mutation):
8+
class Arguments:
9+
project_id = graphene.ID(required=True)
10+
task_id = graphene.ID(required=True)
11+
task_type = graphene.ID(required=True)
12+
13+
ok = graphene.Boolean()
14+
15+
def mutate(self, info, project_id: str, task_id: str, task_type: str):
16+
auth.check_admin_access(info)
17+
if task_type == enums.TaskType.ATTRIBUTE_CALCULATION.value:
18+
manager.cancel_attribute_calculation(project_id, task_id)
19+
elif task_type == enums.TaskType.EMBEDDING.value:
20+
manager.cancel_embedding(project_id, task_id)
21+
elif task_type == enums.TaskType.INFORMATION_SOURCE.value:
22+
manager.cancel_information_source_payload(project_id, task_id)
23+
elif task_type == enums.TaskType.TOKENIZATION.value:
24+
manager.cancel_record_tokenization_task(project_id, task_id)
25+
elif task_type == enums.TaskType.UPLOAD_TASK.value:
26+
manager.cancel_upload_task(project_id, task_id)
27+
elif task_type == enums.TaskType.WEAK_SUPERVISION.value:
28+
manager.cancel_weak_supervision(project_id, task_id)
29+
else:
30+
raise ValueError(f"{task_type} is no valid task type")
31+
return CancelTask(ok=True)
32+
33+
34+
class CancelAllRunningTasks(graphene.Mutation):
35+
ok = graphene.Boolean()
36+
37+
def mutate(self, info):
38+
auth.check_admin_access(info)
39+
manager.cancel_all_running_tasks()
40+
return CancelAllRunningTasks(ok=True)
41+
42+
43+
class CancelInformationSourcePayload(graphene.Mutation):
44+
class Arguments:
45+
payload_id = graphene.ID(required=True)
46+
47+
ok = graphene.Boolean()
48+
49+
def mutate(self, info, payload_id: str):
50+
auth.check_admin_access(info)
51+
manager.cancel_information_source_payload(payload_id=payload_id)
52+
return CancelInformationSourcePayload(ok=True)
53+
54+
55+
class MonitorMutation(graphene.ObjectType):
56+
cancel_task = CancelTask.Field()
57+
cancel_all_running_tasks = CancelAllRunningTasks.Field()

graphql_api/mutation/organization.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ class Arguments:
9999

100100
def mutate(self, info, org_id: str, changes: Dict[str, Any]):
101101
auth.check_demo_access(info)
102-
auth.check_admin_access(info)
102+
if config_service.get_config_value("is_managed"):
103+
auth.check_admin_access(info)
103104
organization_manager.change_organization(org_id, changes)
104105
return DeleteOrganization(ok=True)
105106

graphql_api/query/admin_message.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import graphene
2+
3+
from controller.auth import manager as auth
4+
from graphql_api.types import AdminMessage
5+
from controller.admin_message import manager
6+
7+
8+
class AdminMessageQuery(graphene.ObjectType):
9+
all_admin_messages = graphene.Field(
10+
graphene.List(AdminMessage), limit=graphene.Int()
11+
)
12+
13+
all_active_admin_messages = graphene.Field(
14+
graphene.List(AdminMessage), limit=graphene.Int()
15+
)
16+
17+
def resolve_all_admin_messages(self, info, limit: int = 100) -> AdminMessage:
18+
auth.check_demo_access(info)
19+
auth.check_admin_access(info)
20+
return manager.get_messages(limit, active_only=False)
21+
22+
def resolve_all_active_admin_messages(self, info, limit: int = 100) -> AdminMessage:
23+
auth.check_demo_access(info)
24+
return manager.get_messages(limit, active_only=True)

graphql_api/query/monitor.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import graphene
2+
from graphql_api.types import Task
3+
from controller.auth import manager as auth
4+
from controller.monitor import manager
5+
6+
7+
class MonitorQuery(graphene.ObjectType):
8+
all_tasks = graphene.Field(graphene.List(Task), only_running=graphene.Boolean())
9+
10+
def resolve_all_tasks(self, info, only_running: bool = True):
11+
auth.check_admin_access(info)
12+
return manager.monitor_all_tasks(only_running=only_running)

0 commit comments

Comments
 (0)