Repository: great-expectations/great_expectations

Issue:
User specified run_id is ignored in the validation result json when using WarningAndFailureExpectationSuitesValidationOperator
**The bug**
The `run_id` passed to `data_context.run_validation_operator()` when using `WarningAndFailureExpectationSuitesValidationOperator` is currently ignored. This causes the `run_name` in the validation results json to be `null` and `run_time` to be a system generated validation time instead of the user specified inputs.

**To Reproduce**
Steps to reproduce the behavior:
```
# test.py

import great_expectations as ge
import datetime
import pytz

base_expectation_suite_name = "<base_suite_name>"   # base_suite_name.failure, base_suite_name.warning
data_asset_name = '<DataFile>' # E.g. the logical file name
df = ge.read_csv("<Path to DataFile.csv>")

context = ge.data_context.DataContext()

batch_kwargs = {'data_asset_name': data_asset_name, 'batch_id': "CustomID"}

dataasset = ge.dataset.PandasDataset(df, batch_kwargs = batch_kwargs)

run_id = {
  "run_name": "RunName_A",
  "run_time": datetime.datetime.now(tz=pytz.timezone("Australia/Melbourne"))  # timestamp_A 
}

results = context.run_validation_operator(
    "fail_warn_list_operator",
    base_expectation_suite_name=base_expectation_suite_name,
    assets_to_validate=[dataasset],
    run_id = run_id
    )

print(results.success)

context.open_data_docs()
```

```
# great_expectations.yaml

...

validation_operators:
  fail_warn_list_operator:
    # To learn how to configure sending Slack notifications during evaluation
    # (and other customizations), read: https://docs.greatexpectations.io/en/latest/autoapi/great_expectations/validation_operators/index.html#great_expectations.validation_operators.ActionListValidationOperator
    class_name: WarningAndFailureExpectationSuitesValidationOperator
    expectation_suite_name_suffixes: [".failure", ".warning"]

    action_list:
    - name: store_validation_result
      action:
        class_name: StoreValidationResultAction
    - name: store_evaluation_params
      action:
        class_name: StoreEvaluationParametersAction
    - name: update_data_docs
      action:
        class_name: UpdateDataDocsAction

...

```

```
# validations/base_suite_name/failure/RunName_A/timestamp_A/<uuid>.json

{
  "evaluation_parameters": {},
  "meta": {
    "batch_kwargs": {
      "batch_id": "CustomID",
      "data_asset_name": "DataFile"
    },
    "batch_markers": {},
    "batch_parameters": {},
    "expectation_suite_meta": {
      "columns": {
        "Campus Name": {},
        "Current Date": {},
        "Effective from": {},
        "Field Name": {},
        "Field Value": {},
        "Jurisdiction": {}
      },
      "file": "DataFile",
      "great_expectations_version": "0.13.33+2.g1c962ad49.dirty",
    },
    "expectation_suite_name": "base_suite_name.failure",
    "great_expectations_version": "0.13.33+2.g1c962ad49.dirty",
    "run_id": {
      "run_name": null,   <-- Expect this to be RunName_A
      "run_time": "<validation time>"   <-- Expect this to be timestamp_A
    },
    "validation_time": "<validation time>"
  },
  "results": [ ... ]

# warning validation result also has the same issue
```

**Expected behaviour**
The expected behaviour is `run_name` and `run_time` to take the user specified values when given, otherwise both values to take the system generated time-stamps.

**Environment (please complete the following information):**
 - Operating System: MacOS, but expected to occur in other OSs as well
 - Great Expectations Version: Encountered in 0.13.19 and 0.13.33




Discussion:
I'm working on a solution for this

Resolving patch:
diff --git a/great_expectations/validation_operators/validation_operators.py b/great_expectations/validation_operators/validation_operators.py
--- a/great_expectations/validation_operators/validation_operators.py
+++ b/great_expectations/validation_operators/validation_operators.py
@@ -842,6 +842,7 @@ def run(
                 failure_run_result_obj = {"expectation_suite_severity_level": "failure"}
                 failure_validation_result = batch.validate(
                     failure_expectation_suite,
+                    run_id,
                     result_format=result_format
                     if result_format
                     else self.result_format,
@@ -888,6 +889,7 @@ def run(
                 warning_run_result_obj = {"expectation_suite_severity_level": "warning"}
                 warning_validation_result = batch.validate(
                     warning_expectation_suite,
+                    run_id,
                     result_format=result_format
                     if result_format
                     else self.result_format,


Test patch:
diff --git a/tests/actions/test_validation_operators.py b/tests/actions/test_validation_operators.py
--- a/tests/actions/test_validation_operators.py
+++ b/tests/actions/test_validation_operators.py
@@ -1,5 +1,6 @@
 # TODO: ADD TESTS ONCE GET_BATCH IS INTEGRATED!
 
+import dateutil.parser
 import pandas as pd
 import pytest
 from freezegun import freeze_time
@@ -354,3 +355,64 @@ def test_errors_warnings_validation_operator_succeeded_vo_result_with_only_faile
         ]
     )
     assert return_obj_2.success
+
+
+def test_passing_run_id_as_a_parameter_to_warning_and_failure_vo(
+    warning_failure_validation_operator_data_context, assets_to_validate
+):
+    # this tests whether the run_id, run_name and run_time passed to WarningAndFailureExpectationSuitesValidationOperator
+    # is saved in the validation result.
+
+    data_context = warning_failure_validation_operator_data_context
+
+    vo = WarningAndFailureExpectationSuitesValidationOperator(
+        data_context=data_context,
+        action_list=[],
+        name="test",
+    )
+
+    # pass run id
+    user_run_name = "test_run_name"
+    run_dt = dateutil.parser.parse("2021-09-11 1:47:03+00:00")
+
+    return_obj = vo.run(
+        assets_to_validate=[assets_to_validate[3]],
+        run_id={"run_name": user_run_name, "run_time": run_dt},
+        base_expectation_suite_name="f1",
+    )
+    run_results = list(return_obj.run_results.values())
+
+    assert (
+        run_results[0]["validation_result"]["meta"]["run_id"].run_name == user_run_name
+    )
+    assert run_results[0]["validation_result"]["meta"]["run_id"].run_time == run_dt
+    assert (
+        run_results[1]["validation_result"]["meta"]["run_id"].run_name == user_run_name
+    )
+    assert run_results[1]["validation_result"]["meta"]["run_id"].run_time == run_dt
+
+    # pass run name
+    return_obj = vo.run(
+        assets_to_validate=[assets_to_validate[3]],
+        run_name=user_run_name,
+        base_expectation_suite_name="f1",
+    )
+    run_results = list(return_obj.run_results.values())
+
+    assert (
+        run_results[0]["validation_result"]["meta"]["run_id"].run_name == user_run_name
+    )
+    assert (
+        run_results[1]["validation_result"]["meta"]["run_id"].run_name == user_run_name
+    )
+
+    # pass run time
+    return_obj = vo.run(
+        assets_to_validate=[assets_to_validate[3]],
+        run_time=run_dt,
+        base_expectation_suite_name="f1",
+    )
+    run_results = list(return_obj.run_results.values())
+
+    assert run_results[0]["validation_result"]["meta"]["run_id"].run_time == run_dt
+    assert run_results[1]["validation_result"]["meta"]["run_id"].run_time == run_dt