Repository: mlflow/mlflow
Pull request: adding user_id attribute of RunInfo in search_runs
Signed-off-by: Subramaniam Shanmugam <subramaniam02@gmail.com>

<!-- 🚨 We recommend pull requests be filed from a non-master branch on a repository fork (e.g. <username>:fix-xxx). 🚨 -->

## Related Issues/PRs

<!--
Please reference any related feature requests, issues, or PRs here. For example, `#123`. To automatically close the referenced items when this PR is merged, please use a closing keyword (close, fix, or resolve). For example, `Close #123`. See https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue for more information.
-->
closes #6748 

## What changes are proposed in this pull request?

user_id support for search_runs

## How is this patch tested?

<!--
If you're unsure about what to test, where to add tests, or how to run tests, please feel free to ask. We'd be happy to help.
-->
- [x] I have written tests (not required for typo or doc fix) and confirmed the proposed feature/bug-fix/change works.

<!--
Please describe how you confirmed the proposed feature/bug-fix/change works here. For example, if you fixed an MLflow client API, you could attach the code that didn't work prior to the fix but works now, or if you added a new feature on MLflow UI, you could attach a video that demonstrates the feature.
-->

## Does this PR change the documentation?

- [ ] No. You can skip the rest of this section.
- [x] Yes. Make sure the changed pages / sections render correctly by following the steps below.

1. Click the `Details` link on the `Preview docs` check.
2. Find the changed pages / sections and make sure they render correctly.

## Release Notes

### Is this a user-facing change?

- [ ] No. You can skip the rest of this section.
- [x] Yes. Give a description of this change to be included in the release notes for MLflow users.

search_runs supports user_id attribute of RunInfo entity

### What component(s), interfaces, languages, and integrations does this PR affect?
Components
- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/pipelines`: Pipelines, Pipeline APIs, Pipeline configs, Pipeline Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [x] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface
- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

<!--
Insert an empty named anchor here to allow jumping to this section with a fragment URL
(e.g. https://github.com/mlflow/mlflow/pull/123#user-content-release-note-category).
Note that GitHub prefixes anchor names in markdown with "user-content-".
-->
<a name="release-note-category"></a>
### How should the PR be classified in the release notes? Choose one:

- [ ] `rn/breaking-change` - The PR will be mentioned in the "Breaking Changes" section
- [ ] `rn/none` - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
- [x] `rn/feature` - A new user-facing feature worth mentioning in the release notes
- [ ] `rn/bug-fix` - A user-facing bug fix worth mentioning in the release notes
- [ ] `rn/documentation` - A user-facing documentation change worth mentioning in the release notes


Problem statement:
[FR] Add the ability to search runs based on 'user_id'
### Willingness to contribute

No. I cannot contribute this feature at this time.

### Proposal Summary

Enable run search to perform searches based on the `user_id` that submitted the run. 

### Motivation

> #### What is the use case for this feature?
To return runs that have been initiated by a particular user while using run search functionality.

> #### Why is this use case valuable to support for MLflow users in general?
Quality of life improvement for navigating within the UI

> #### Why is this use case valuable to support for your project(s) or organization?

> #### Why is it currently difficult to achieve this use case?
It is not supported right now.


### Details

_No response_

### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/pipelines`: Pipelines, Pipeline APIs, Pipeline configs, Pipeline Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [X] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

Hints from issue discussion:
<!-- assign-maintainer -->
@BenWilson2 @dbczumar @harupy @WeichenXu123 Please assign a maintainer and start triaging this issue.

Commit message: user_id filter for search_runs

Signed-off-by: Subramaniam Shanmugam <subramaniam02@gmail.com>

Patch to review:
diff --git a/docs/source/search-runs.rst b/docs/source/search-runs.rst
index 5a866a9988660..2a508c73ee8a6 100644
--- a/docs/source/search-runs.rst
+++ b/docs/source/search-runs.rst
@@ -98,6 +98,7 @@ You can search using two run attributes contained in :py:class:`mlflow.entities.
 .. code-block:: sql
 
   attributes.artifact_uri
+  attributes.user_id
 
 
 .. _mlflow_tags:
diff --git a/mlflow/entities/run_info.py b/mlflow/entities/run_info.py
index c42b0e23bf5f3..ac4cb1145124d 100644
--- a/mlflow/entities/run_info.py
+++ b/mlflow/entities/run_info.py
@@ -106,7 +106,7 @@ def run_name(self):
         """String containing run name."""
         return self._run_name
 
-    @property
+    @searchable_attribute
     def user_id(self):
         """String ID of the user who initiated this run."""
         return self._user_id
diff --git a/tests/entities/test_run_info.py b/tests/entities/test_run_info.py
index 6f69d2ac00b15..0a781701549c1 100644
--- a/tests/entities/test_run_info.py
+++ b/tests/entities/test_run_info.py
@@ -147,5 +147,6 @@ def test_creation_and_hydration(self):
 
     def test_searchable_attributes(self):
         self.assertSequenceEqual(
-            {"status", "artifact_uri", "start_time"}, set(RunInfo.get_searchable_attributes())
+            {"status", "artifact_uri", "start_time", "user_id"},
+            set(RunInfo.get_searchable_attributes()),
         )
diff --git a/tests/utils/test_search_utils.py b/tests/utils/test_search_utils.py
index 1d19c4a10f020..ca1c8b7ed253b 100644
--- a/tests/utils/test_search_utils.py
+++ b/tests/utils/test_search_utils.py
@@ -323,6 +323,38 @@ def test_filter_runs_by_start_time():
     assert SearchUtils.filter(runs, "attribute.start_time = 2") == runs[2:]
 
 
+def test_filter_runs_by_user_id():
+    runs = [
+        Run(
+            run_info=RunInfo(
+                run_uuid="a",
+                run_id="a",
+                experiment_id=0,
+                user_id="user-id",
+                status=RunStatus.to_string(RunStatus.FINISHED),
+                start_time=1,
+                end_time=1,
+                lifecycle_stage=LifecycleStage.ACTIVE,
+            ),
+            run_data=RunData(),
+        ),
+        Run(
+            run_info=RunInfo(
+                run_uuid="b",
+                run_id="b",
+                experiment_id=0,
+                user_id="user-id2",
+                status=RunStatus.to_string(RunStatus.FINISHED),
+                start_time=1,
+                end_time=1,
+                lifecycle_stage=LifecycleStage.ACTIVE,
+            ),
+            run_data=RunData(),
+        ),
+    ]
+    assert SearchUtils.filter(runs, "attribute.user_id = 'user-id2'") == runs[1]
+
+
 @pytest.mark.parametrize(
     ("order_bys", "matching_runs"),
     [


Code review comments:
diff_hunk: @@ -323,6 +323,38 @@ def test_filter_runs_by_start_time():
     assert SearchUtils.filter(runs, "attribute.start_time = 2") == runs[2:]
 
 
+def test_filter_runs_by_user_id():
+    runs = [
+        Run(
+            run_info=RunInfo(
+                run_uuid="a",
+                run_id="a",
+                experiment_id=0,
+                user_id="user-id",
+                status=RunStatus.to_string(RunStatus.FINISHED),
+                start_time=1,
+                end_time=1,
+                lifecycle_stage=LifecycleStage.ACTIVE,
+            ),
+            run_data=RunData(),
+        ),
+        Run(
+            run_info=RunInfo(
+                run_uuid="b",
+                run_id="b",
+                experiment_id=0,
+                user_id="user-id2",
+                status=RunStatus.to_string(RunStatus.FINISHED),
+                start_time=1,
+                end_time=1,
+                lifecycle_stage=LifecycleStage.ACTIVE,
+            ),
+            run_data=RunData(),
+        ),
+    ]
+    assert SearchUtils.filter(runs, "attribute.user_id = 'user-id2'") == runs[1]
path: tests/utils/test_search_utils.py
text: @user1:
```suggestion
    assert SearchUtils.filter(runs, "attribute.user_id = 'user-id2'")[0] == runs[1]
```

@author:
Sorry about the miss. Fixed.