Skip to content

feat: Search created by user in pipeline run API#108

Open
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-annotationsfrom
ycq/search-pipeline-run-created-by
Open

feat: Search created by user in pipeline run API#108
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-annotationsfrom
ycq/search-pipeline-run-created-by

Conversation

@yuechao-qin
Copy link
Collaborator

@yuechao-qin yuechao-qin commented Feb 24, 2026

TL;DR

Implemented search created_by user in Pipeline Runs.

What changed?

Functionality

  • API GET /api/pipeline_runs/

    • Search created_by user in filter_query. Example query (not URL encoded for example):
      /api?filter_query={"and": [{"value_equals": {"key": "system/pipeline_run.created_by", "value": "alice@example.com"}}]}
    • Annotation value me is supported for created_by search.
  • API POST /api/pipeline_runs/

    • Copies the created_by data to the annotations table for future searching. Example pipeline run annotations table after API is called:
      pipeline_run_id key value
      abc123 system/pipeline_run.created_by alice@example.com
  • API [PUT|DELETE] /api/pipeline_runs/{id}/annotations/{key}

    • Prevent (create, update, delete) of annotations with system/ prefix. Reserved for system annotation usage.

Other

  • Database migration: Added backfill logic to populate existing pipeline runs with created_by annotations. Example what a pipeline run's created by user would look like in the annotations table:
pipeline_run_id key value
42 system/pipeline_run.created_by alice@example.com

How to test?

uv run pytest tests/test_api_server_sql.py  tests/test_filter_query_sql.py tests/test_database_ops.py
  1. Create pipeline runs with different created_by values
  2. Use filter queries like {"and": [{"value_equals": {"key": "system/pipeline_run.created_by", "value": "alice"}}]} to search by creator
  3. Test the "me" placeholder: {"and": [{"value_equals": {"key": "system/pipeline_run.created_by", "value": "me"}}]}
  4. Verify that attempts to set/delete system annotations return 422 errors
  5. Test that unsupported predicates on system keys (like value_contains) are rejected

Why make this change?

  • This enables users to search for pipeline runs by creator using the new filter query system.
  • Following safety guards and synchronization will allow old and new data (create_by) to be searchable with the new filter.
    • Preventing (create, delete, update) system prefix in annotations
    • Saving created_by to annotations table when starting a Pipeline Run

@yuechao-qin yuechao-qin marked this pull request as ready for review February 24, 2026 17:06
@yuechao-qin yuechao-qin requested a review from Ark-kun as a code owner February 24, 2026 17:06
Copy link
Collaborator

@Volv-G Volv-G left a comment

Choose a reason for hiding this comment

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

looks good to me

@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-annotations to graphite-base/108 February 25, 2026 06:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 6789217 to 01de682 Compare February 25, 2026 06:39
@yuechao-qin yuechao-qin changed the base branch from graphite-base/108 to ycq/search-pipeline-run-annotations February 25, 2026 06:39
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 01de682 to 9a3927b Compare February 25, 2026 06:43
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from 7606c83 to 98e7d41 Compare February 25, 2026 18:45
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 9a3927b to 0e3ac9b Compare February 25, 2026 18:45
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from 98e7d41 to ecd7842 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 0e3ac9b to df1b586 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-annotations branch from ecd7842 to ba5594e Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from df1b586 to 05e42b2 Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-annotations to graphite-base/108 February 28, 2026 10:23
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 05e42b2 to afa68d5 Compare February 28, 2026 10:24
@yuechao-qin yuechao-qin changed the base branch from graphite-base/108 to ycq/search-pipeline-run-annotations February 28, 2026 10:24
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from afa68d5 to 527caa1 Compare February 28, 2026 19:15
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-created-by branch from 527caa1 to 7b5d7cb Compare February 28, 2026 20:29
backfill_created_by_annotations(db_engine=db_engine)


def is_annotation_key_already_backfilled(
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. It's a public function. Let's make it private.
  2. The name mentions "annotation", but that work is used everywhere. We need to be more specific.

_is_pipeline_run_annotation_key_already_backfilled

).scalar()


def backfill_created_by_annotations(*, db_engine: sqlalchemy.Engine):
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. It's a public function. Let's make it private.
  2. The name mentions "annotation", but that work is used everywhere. We need to be more specific.

_backfill_pipeline_run_created_by_annotations

SYSTEM_KEY_PREFIX: Final[str] = "system/"


class SystemKey(enum.StrEnum):
Copy link
Contributor

Choose a reason for hiding this comment

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

PipelineRunAnnotationSystemKey

def _get_predicate_key(*, predicate: filter_query_models.Predicate) -> str | None:
"""Extract the annotation key from a leaf predicate, or None for logical operators."""
match predicate:
case filter_query_models.KeyExistsPredicate():
Copy link
Contributor

Choose a reason for hiding this comment

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

You can have some base class like filter_query_models.KeyPredicateBase with field key. Then getting the key from the predicates would be trivial.

*,
predicate,
predicate: filter_query_models.Predicate,
current_user: str | None = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's simpler to first preprocess the predicates so that you don't need to do that in functions like _predicate_to_clause ?


@pytest.fixture()
def session_factory():
engine = sqlalchemy.create_engine(
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use the create_db_engine function from database_ops (without migration).


class TestIsAnnotationKeyAlreadyBackfilled:
def test_false_on_empty_db(self, session_factory):
engine = session_factory.kw["bind"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe, is_annotation_key_already_backfilled can just accept session_factory instead of engine?

skip_user_check: bool = False,
):
if key.startswith(filter_query_sql.SYSTEM_KEY_PREFIX):
raise errors.InvalidAnnotationKeyError(_SYSTEM_KEY_RESERVED_MSG)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe, self._fail_if_changing_system_annotation(key)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants