Skip to content

Vb/search projects filters plt 1399 #1771

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

Merged
merged 6 commits into from
Aug 20, 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
2 changes: 2 additions & 0 deletions docs/labelbox/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Labelbox Python SDK Documentation
labeling-frontend-options
labeling-parameter-override
labeling-service
labeling-service-dashboard
model
model-config
model-run
Expand All @@ -42,6 +43,7 @@ Labelbox Python SDK Documentation
quality-mode
resource-tag
review
search-filters
send-to-annotate-params
slice
task
Expand Down
5 changes: 5 additions & 0 deletions docs/labelbox/labeling-service-dashboard.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Labeling Service Dashboard
===============================================================================================

.. automodule:: labelbox.schema.labeling_service_dashboard
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/labelbox/labeling-service-status.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Labeling Service Status
===============================================================================================

.. automodule:: labelbox.schema.labeling_service_status
:members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/labelbox/search-filters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Search Filters
===============================================================================================

.. automodule:: labelbox.schema.search_filters
:members:
:exclude-members: _dict_to_graphql_string
:show-inheritance:
38 changes: 28 additions & 10 deletions libs/labelbox/src/labelbox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from datetime import datetime, timezone
from typing import Any, List, Dict, Union, Optional, overload, Callable

from labelbox.schema.search_filters import SearchFilter
import requests
import requests.exceptions
from google.api_core import retry
Expand Down Expand Up @@ -2409,21 +2410,38 @@ def upsert_label_feedback(self, label_id: str, feedback: str,

def get_labeling_service_dashboards(
self,
after: Optional[str] = None,
search_query: Optional[List[Dict]] = None,
search_query: Optional[List[SearchFilter]] = None,
) -> PaginatedCollection:
"""
Get all labeling service dashboards for a given org.
Optional parameters:
after: The cursor to use for pagination.
where: A filter to apply to the query.
NOTE: support for after and search_query are not yet implemented.
"""
return LabelingServiceDashboard.get_all(self,
after,
search_query=search_query)
search_query: A list of search filters representing the search
NOTE:
- Retrieves all projects for the organization or as filtered by the search query.
- Sorted by project created date in ascending order.
Examples:
Retrieves all labeling service dashboards for a given workspace id:
>>> workspace_filter = WorkspaceFilter(
>>> operation=OperationType.Workspace,
>>> operator=IdOperator.Is,
>>> values=[workspace_id])
>>> labeling_service_dashboard = [
>>> ld for ld in project.client.get_labeling_service_dashboards(search_query=[workspace_filter])]
Retrieves all labeling service dashboards requested less than 7 days ago:
>>> seven_days_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
>>> workforce_requested_filter_before = WorkforceRequestedDateFilter(
>>> operation=OperationType.WorforceRequestedDate,
>>> value=DateValue(operator=DateOperator.GreaterThanOrEqual,
>>> value=seven_days_ago))
>>> labeling_service_dashboard = [ld for ld in project.client.get_labeling_service_dashboards(search_query=[workforce_requested_filter_before])]
See libs/labelbox/src/labelbox/schema/search_filters.py and libs/labelbox/tests/unit/test_unit_search_filters.py for more examples.
"""
return LabelingServiceDashboard.get_all(self, search_query=search_query)

def get_task_by_id(self, task_id: str) -> Union[Task, DataUpsertTask]:
"""
Expand Down
39 changes: 24 additions & 15 deletions libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from labelbox.exceptions import ResourceNotFoundError
from labelbox.pagination import PaginatedCollection
from labelbox.pydantic_compat import BaseModel, root_validator, Field
from labelbox.schema.search_filters import SearchFilter, build_search_filter
from labelbox.utils import _CamelCaseMixin
from labelbox.schema.labeling_service_status import LabelingServiceStatus

Expand Down Expand Up @@ -94,28 +95,36 @@ def get(cls, client, project_id: str) -> 'LabelingServiceDashboard':
def get_all(
cls,
client,
after: Optional[str] = None,
search_query: Optional[List[Dict]] = None,
search_query: Optional[List[SearchFilter]] = None,
) -> PaginatedCollection:
template = Template(
"""query SearchProjectsPyApi($$first: Int, $$from: String) {
searchProjects(input: {after: $$from, searchQuery: $search_query, size: $$first})
{
nodes { $labeling_dashboard_selections }
pageInfo { endCursor }

if search_query is not None:
template = Template(
"""query SearchProjectsPyApi($$first: Int, $$from: String) {
searchProjects(input: {after: $$from, searchQuery: $search_query, size: $$first})
{
nodes { $labeling_dashboard_selections }
pageInfo { endCursor }
}
}
""")
else:
template = Template(
"""query SearchProjectsPyApi($$first: Int, $$from: String) {
searchProjects(input: {after: $$from, size: $$first})
{
nodes { $labeling_dashboard_selections }
pageInfo { endCursor }
}
}
}
""")
organization_id = client.get_organization().uid
""")
query_str = template.substitute(
labeling_dashboard_selections=GRAPHQL_QUERY_SELECTIONS,
search_query=
f"[{{type: \"organization\", operator: \"is\", values: [\"{organization_id}\"]}}]"
search_query=build_search_filter(search_query)
if search_query else None,
)

params: Dict[str, Union[str, int]] = {}
if after:
params = {"from": after}

def convert_to_labeling_service_dashboard(client, data):
data['client'] = client
Expand Down
187 changes: 187 additions & 0 deletions libs/labelbox/src/labelbox/schema/search_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import datetime
from enum import Enum
from typing import List, Literal, Union

from labelbox.pydantic_compat import BaseModel
from labelbox.utils import format_iso_datetime


class BaseSearchFilter(BaseModel):
"""
Shared code for all search filters
"""

class Config:
use_enum_values = True

def dict(self, *args, **kwargs):
res = super().dict(*args, **kwargs)
if 'operation' in res:
res['type'] = res.pop('operation')

# go through all the keys and convert date to string
for key in res:
if isinstance(res[key], datetime.datetime):
res[key] = format_iso_datetime(res[key])
return res


class OperationType(Enum):
"""
Supported search entity types
"""
Organization = 'organization'
Workspace = 'workspace'
Tag = 'tag'
Stage = 'stage'
WorforceRequestedDate = 'workforce_requested_at'
WorkforceStageUpdatedDate = 'workforce_stage_updated_at'


class IdOperator(Enum):
"""
Supported operators for ids
"""
Is = 'is'


class DateOperator(Enum):
"""
Supported operators for dates
"""
Equals = 'EQUALS'
GreaterThanOrEqual = 'GREATER_THAN_OR_EQUAL'
LessThanOrEqual = 'LESS_THAN_OR_EQUAL'


class DateRangeOperator(Enum):
"""
Supported operators for date ranges
"""
Between = 'BETWEEN'


class OrganizationFilter(BaseSearchFilter):
"""
Filter for organization
"""
operation: Literal[OperationType.Organization]
operator: IdOperator
values: List[str]


class WorkspaceFilter(BaseSearchFilter):
"""
Filter for workspace
"""
operation: Literal[OperationType.Workspace]
operator: IdOperator
values: List[str]


class TagFilter(BaseSearchFilter):
"""
Filter for project tags
"""
operation: Literal[OperationType.Tag]
operator: IdOperator
values: List[str]


class ProjectStageFilter(BaseSearchFilter):
"""
Filter labelbox service / aka project stages
"""
operation: Literal[OperationType.Stage]
operator: IdOperator
values: List[str]


class DateValue(BaseSearchFilter):
"""
Date value for a search filter
Date formats:
datetime: an existing datetime object
str the following formats are accepted: YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]
NOTE
if a date / datetime string is passed without a timezone, we will assume the time is UTC and convert it to a local timezone
so for a string '2024-01-01' that is run on a computer in PST, we would convert it to '2024-01-01T08:00:00Z'
while the same string in EST will get converted to '2024-01-01T05:00:00Z'
"""
operator: DateOperator
value: datetime.datetime


class WorkforceStageUpdatedFilter(BaseSearchFilter):
"""
Filter for workforce stage updated date
"""
operation: Literal[OperationType.WorkforceStageUpdatedDate]
value: DateValue


class WorkforceRequestedDateFilter(BaseSearchFilter):
"""
Filter for workforce requested date
"""
operation: Literal[OperationType.WorforceRequestedDate]
value: DateValue


class DateRange(BaseSearchFilter):
"""
Date range for a search filter
"""
min: datetime.datetime
max: datetime.datetime


class DateRangeValue(BaseSearchFilter):
"""
Date range value for a search filter
"""
operator: DateRangeOperator
value: DateRange


class WorkforceRequestedDateRangeFilter(BaseSearchFilter):
"""
Filter for workforce requested date range
"""
operation: Literal[OperationType.WorforceRequestedDate]
value: DateRangeValue


class WorkforceStageUpdatedRangeFilter(BaseSearchFilter):
"""
Filter for workforce stage updated date range
"""
operation: Literal[OperationType.WorkforceStageUpdatedDate]
value: DateRangeValue


SearchFilter = Union[OrganizationFilter, WorkspaceFilter, TagFilter,
ProjectStageFilter, WorkforceRequestedDateFilter,
WorkforceStageUpdatedFilter,
WorkforceRequestedDateRangeFilter,
WorkforceStageUpdatedRangeFilter]


def _dict_to_graphql_string(d: Union[dict, list, str, int]) -> str:
if isinstance(d, dict):
return "{" + ", ".join(
f'{k}: {_dict_to_graphql_string(v)}' for k, v in d.items()) + "}"
elif isinstance(d, list):
return "[" + ", ".join(
_dict_to_graphql_string(item) for item in d) + "]"
else:
return f'"{d}"' if isinstance(d, str) else str(d)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to surround numbers with quotations too? Maybe doesn't matter currently since I don't think any of the filters currently use one, but if we do we should do that now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the quotations are only if isinstance(d, str). You are right we need to test this code with numbers. Currently we do not have any, but I will do it in a follow up story for a new filter we are creating



def build_search_filter(filter: List[SearchFilter]):
"""
Converts a list of search filters to a graphql string
"""
filters = [_dict_to_graphql_string(f.dict()) for f in filter]
return "[" + ", ".join(filters) + "]"
18 changes: 18 additions & 0 deletions libs/labelbox/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,3 +1096,21 @@ def embedding(client: Client, environ):
@pytest.fixture
def valid_model_id():
return "2c903542-d1da-48fd-9db1-8c62571bd3d2"


@pytest.fixture
def requested_labeling_service(
rand_gen, live_chat_evaluation_project_with_new_dataset,
chat_evaluation_ontology, model_config):
project = live_chat_evaluation_project_with_new_dataset
project.connect_ontology(chat_evaluation_ontology)

project.upsert_instructions('tests/integration/media/sample_pdf.pdf')

labeling_service = project.get_labeling_service()
project.add_model_config(model_config.uid)
project.set_project_model_setup_complete()

labeling_service.request()

yield project, labeling_service
Loading
Loading