Skip to content

Commit

Permalink
workforce management
Browse files Browse the repository at this point in the history
  • Loading branch information
pritamrungta committed Jan 27, 2025
1 parent cc89f09 commit b7f5821
Show file tree
Hide file tree
Showing 19 changed files with 605 additions and 58 deletions.
12 changes: 9 additions & 3 deletions docs/sdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ functions for programmatically manipulating data, importing annotations, assigni
RedBrick
----------------------
.. automodule:: redbrick
:members: get_org, get_workspace, get_project, get_org_from_profile, get_project_from_profile, StorageMethod, ImportTypes, TaskEventTypes, TaskFilters, TaskStates, ProjectMemberRole, Stage, LabelStage, ReviewStage, ModelStage
:members: get_org, get_workspace, get_project, get_org_from_profile, get_project_from_profile, StorageMethod, ImportTypes, TaskEventTypes, TaskFilters, TaskStates, OrgMemberRole, ProjectMemberRole, Stage, LabelStage, ReviewStage, ModelStage, ProjectMember, ProjectMemberInput
:member-order: bysource

.. _org:

Organization
----------------------
.. autoclass:: redbrick.organization.RBOrganization
:members: name, org_id, create_workspace, create_project, create_project_advanced, get_project, projects_raw, projects, delete_project, taxonomies, labeling_time, create_taxonomy, get_taxonomy, update_taxonomy, delete_taxonomy
:members: name, org_id, create_workspace, create_project, create_project_advanced, get_project, projects_raw, projects, members, delete_project, taxonomies, labeling_time, create_taxonomy, get_taxonomy, update_taxonomy, delete_taxonomy

Workspace
----------------------
.. autoclass:: redbrick.workspace.RBWorkspace
:members: name, org_id, workspace_id, metadata_schema, classification_schema, cohorts, update_schema, update_cohorts, get_datapoints, create_datapoints, archive_datapoints, unarchive_datapoints, delete_datapoints, add_datapoints_to_cohort, add_datapoints_to_projects, remove_datapoints_from_cohort, update_datapoint_attributes
:members: name, org_id, workspace_id, metadata_schema, classification_schema, cohorts, update_schema, update_cohorts, get_datapoints, create_datapoints, archive_datapoints, unarchive_datapoints, delete_datapoints, add_datapoints_to_cohort, add_datapoints_to_projects, remove_datapoints_from_cohort, update_datapoint_attributes, update_datapoints_metadata
:show-inheritance:

.. _project:
Expand Down Expand Up @@ -55,3 +55,9 @@ Settings
.. autoclass:: redbrick.settings.Settings
:members: label_validation, hanging_protocol, webhook, toggle_reference_standard_task, task_duplication
:show-inheritance:

Workforce
----------------------
.. autoclass:: redbrick.workforce.Workforce
:members: get_member, list_members, add_members, update_members, remove_members
:show-inheritance:
7 changes: 7 additions & 0 deletions redbrick/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
TaskEventTypes,
TaskFilters,
TaskStates,
OrgMemberRole,
ProjectMemberRole,
)
from redbrick.common.constants import DEFAULT_URL
from redbrick.organization import RBOrganization
from redbrick.workspace import RBWorkspace
from redbrick.project import RBProject
from redbrick.stage import Stage, LabelStage, ReviewStage, ModelStage
from redbrick.common.workforce import ProjectMember, ProjectMemberInput

from redbrick.utils.logging import logger
from redbrick.utils.common_utils import config_migration
Expand Down Expand Up @@ -79,6 +81,7 @@ def _populate_context(context: RBContext) -> RBContext:
SettingsRepo,
ProjectRepo,
WorkspaceRepo,
WorkforceRepo,
)

if context.config.debug:
Expand All @@ -90,6 +93,7 @@ def _populate_context(context: RBContext) -> RBContext:
context.settings = SettingsRepo(context.client)
context.project = ProjectRepo(context.client)
context.workspace = WorkspaceRepo(context.client)
context.workforce = WorkforceRepo(context.client)
return context


Expand Down Expand Up @@ -243,11 +247,14 @@ def get_project_from_profile(
"TaskEventTypes",
"TaskFilters",
"TaskStates",
"OrgMemberRole",
"ProjectMemberRole",
"Stage",
"LabelStage",
"ReviewStage",
"ModelStage",
"ProjectMember",
"ProjectMemberInput",
"RBOrganization",
"RBWorkspace",
"RBProject",
Expand Down
2 changes: 2 additions & 0 deletions redbrick/common/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self, api_key: str, url: str) -> None:
from .settings import SettingsControllerInterface
from .project import ProjectRepoInterface
from .workspace import WorkspaceRepoInterface
from .workforce import WorkforceControllerInterface

self.config = config
self.client = RBClient(api_key=api_key, url=url)
Expand All @@ -28,6 +29,7 @@ def __init__(self, api_key: str, url: str) -> None:
self.settings: SettingsControllerInterface
self.project: ProjectRepoInterface
self.workspace: WorkspaceRepoInterface
self.workforce: WorkforceControllerInterface

self._key_id: Optional[str] = None

Expand Down
18 changes: 16 additions & 2 deletions redbrick/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,29 @@ class TaskFilters(str, Enum):
ISSUES = "ISSUES"


class OrgMemberRole(str, Enum):
"""Enumerate access levels for Organization.
- ``OWNER`` - Organization Owner
- ``ADMIN`` - Organization Admin
- ``MEMBER`` - Organization Member
"""

OWNER = "OWNER"
ADMIN = "ADMIN"
MEMBER = "MEMBER"


class ProjectMemberRole(str, Enum):
"""Enumerate access levels for Project.
- ``ADMIN`` - Project Admin
- ``MANAGER`` - Project Manager
- ``LABELER`` - Project Labeler
- ``MEMBER`` - Project Member (Labeler/Reviewer)
"""

ADMIN = "ADMIN"
MANAGER = "MANAGER"
LABELER = "LABELER"
MEMBER = "MEMBER"
4 changes: 0 additions & 4 deletions redbrick/common/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ def post_process(self, org_id: str, project_id: str, config: Dict) -> None:
def get_current_user(self) -> Dict:
"""Get current user."""

@abstractmethod
def get_members(self, org_id: str, project_id: str) -> List[Dict]:
"""Get members of a project."""

@abstractmethod
def self_health_check(
self, org_id: str, self_url: str, self_data: Dict
Expand Down
110 changes: 110 additions & 0 deletions redbrick/common/workforce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Abstract interface to project workforce."""

from dataclasses import dataclass
from typing import Dict, List, Optional, TypedDict
from abc import ABC, abstractmethod
from typing_extensions import NotRequired # type: ignore

from redbrick.common.enums import OrgMemberRole, ProjectMemberRole


@dataclass
class ProjectMember:
"""Project Member.
Parameters
--------------
user_id: str
User ID
email: str
User email
given_name: str
User given name
family_name: str
User family name
org_role: OrgMemberRole
User role in organization
project_role: ProjectMemberRole
User role in project
tags: List[str]
Tags associated with the user
stages: Optional[List[str]] = None
Stages that the member has access to (Applicable for MEMBER role)
"""

user_id: str
email: str
given_name: str
family_name: str
org_role: OrgMemberRole
project_role: ProjectMemberRole
tags: List[str]
stages: Optional[List[str]] = None

@classmethod
def from_entity(cls, member: Dict) -> "ProjectMember":
"""Get object from entity."""
role = ProjectMemberRole(
"MEMBER" if member["role"] == "LABELER" else member["role"]
)
return cls(
user_id=member["member"]["user"]["userId"],
email=member["member"]["user"]["email"],
given_name=member["member"]["user"]["givenName"],
family_name=member["member"]["user"]["familyName"],
org_role=OrgMemberRole(member["member"]["role"]),
project_role=role,
tags=member["member"]["tags"],
stages=(
[
stage["stageName"]
for stage in member["stageAccess"]
if stage["access"]
]
if role == ProjectMemberRole.MEMBER
else None
),
)


@dataclass
class ProjectMemberInput(TypedDict):
"""Project Member Input."""

#: Member ID (Either unique email or userId)
member_id: str

#: Member role
role: ProjectMemberRole

#: Stages that the member has access to (Applicable for MEMBER role)
stages: NotRequired[List[str]]


class WorkforceControllerInterface(ABC):
"""Abstract interface to define methods for Member."""

@abstractmethod
def org_members(self, org_id: str) -> List[Dict]:
"""Get a list of all org members."""

@abstractmethod
def list_members(self, org_id: str, project_id: str) -> List[Dict]:
"""Get a list of all project members."""

@abstractmethod
def update_memberships(
self, org_id: str, project_id: str, memberships: List[Dict]
) -> None:
"""Update project memberships."""

@abstractmethod
def remove_members(self, org_id: str, project_id: str, user_ids: List[str]) -> None:
"""Remove project members."""
6 changes: 3 additions & 3 deletions redbrick/export/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,7 @@ def list_tasks(
else:
raise ValueError(f"Invalid task filter: {search}")

members = self.context.project.get_members(self.org_id, self.project_id)
members = self.context.workforce.list_members(self.org_id, self.project_id)
users = {}
for member in members:
user = member.get("member", {}).get("user", {})
Expand Down Expand Up @@ -1291,7 +1291,7 @@ def get_task_events(
}]
"""
# pylint: disable=too-many-locals
members = self.context.project.get_members(self.org_id, self.project_id)
members = self.context.workforce.list_members(self.org_id, self.project_id)
users = {}
for member in members:
user = member.get("member", {}).get("user", {})
Expand Down Expand Up @@ -1371,7 +1371,7 @@ def get_active_time(
"cycle": number # Task cycle
}]
"""
members = self.context.project.get_members(self.org_id, self.project_id)
members = self.context.workforce.list_members(self.org_id, self.project_id)
users = {}
for member in members:
user = member.get("member", {}).get("user", {})
Expand Down
25 changes: 25 additions & 0 deletions redbrick/organization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from typing import Any, List, Optional, Dict, Sequence, Union
import platform

from dateutil import parser # type: ignore
from tqdm import tqdm # type: ignore

from redbrick.common.enums import OrgMemberRole
from redbrick.config import config
from redbrick.common.context import RBContext
from redbrick.project import RBProject
Expand Down Expand Up @@ -76,6 +78,29 @@ def projects(self) -> List[RBProject]:
for proj in tqdm(projects, leave=config.log_info)
]

@property
def members(self) -> List[Dict]:
"""Get list of org members."""
members = self.context.workforce.org_members(self.org_id)
org_members = []
for member in members:
user = member["user"]
org_members.append(
{
"userId": user["userId"],
"email": user["email"],
"givenName": user["givenName"],
"familyName": user["familyName"],
"role": OrgMemberRole(member["role"]),
"tags": member["tags"],
"is2FAEnabled": bool(user["mfaSetup"]),
"lastActive": parser.parse(
member.get("lastSeen", user.get("lastSeen", user["updatedAt"]))
),
}
)
return org_members

@property
def org_id(self) -> str:
"""Retrieve the unique org_id of this organization."""
Expand Down
10 changes: 8 additions & 2 deletions redbrick/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, context: RBContext, org_id: str, project_id: str) -> None:
from redbrick.labeling import Labeling
from redbrick.export import Export
from redbrick.settings import Settings
from redbrick.workforce import Workforce

self.context = context

Expand Down Expand Up @@ -91,6 +92,7 @@ def __init__(self, context: RBContext, org_id: str, project_id: str) -> None:
review_stages,
)
self.settings = Settings(context, org_id, project_id, taxonomy)
self.workforce = Workforce(context, org_id, project_id)

@property
def org_id(self) -> str:
Expand Down Expand Up @@ -175,8 +177,12 @@ def stages(self) -> List[Stage]:

@property
def members(self) -> List[Dict]:
"""Get list of project members."""
members = self.context.project.get_members(self.org_id, self.project_id)
"""Get list of project members.
.. deprecated:: 2.21.0
Please use :func:`project.workforce.list_members` instead.
"""
members = self.context.workforce.list_members(self.org_id, self.project_id)
project_members = []
for member in members:
member_obj = member.get("member", {})
Expand Down
1 change: 1 addition & 0 deletions redbrick/repo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
from .settings import SettingsRepo
from .project import ProjectRepo
from .workspace import WorkspaceRepo
from .workforce import WorkforceRepo
27 changes: 0 additions & 27 deletions redbrick/repo/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,33 +460,6 @@ def get_current_user(self) -> Dict:
current_user: Dict = result["me"]
return current_user

def get_members(self, org_id: str, project_id: str) -> List[Dict]:
"""Get members of a project."""
query_string = """
query getProjectMembersSDK($orgId: UUID!, $projectId: UUID!) {
projectMembers(orgId: $orgId, projectId: $projectId) {
member {
user {
userId
email
givenName
familyName
}
role
tags
}
stageAccess {
stageName
access
}
}
}
"""
query_variables = {"orgId": org_id, "projectId": project_id}
result = self.client.execute_query(query_string, query_variables)
members: List[Dict] = result["projectMembers"]
return members

def self_health_check(
self, org_id: str, self_url: str, self_data: Dict
) -> Optional[str]:
Expand Down
Loading

0 comments on commit b7f5821

Please sign in to comment.