Repository: great-expectations/great_expectations

Issue:
Data Docs build does not build suite pages if being run via a checkpoint and does not upload assets
**Describe the bug**
Data Docs build does not build suite pages if using a GCS bucket

**To Reproduce**
Steps to reproduce the behavior:
1. Create a number of suites
2. Set up a GCS site
3. `great_expectations docs build`
4. Note a GCS site with an index page only - no other files or assets.

**Expected behavior**
I expect a full datadocs site

**Environment (please complete the following information):**
 - OS: [e.g. iOS] macOS
 - GE Version: [e.g. 0.10.0] 0.12.3



Discussion:
<img width="1636" alt="Data_Docs_created_by_Great_Expectations" src="https://user-images.githubusercontent.com/928247/94960938-f2407d80-04b0-11eb-9077-125ace0870ec.png">

FYI while trying to reproduce this: currently `great_expectations docs build` seems to work as expected, however this issue does crop up when using `great_expectations checkpoint run ...` 
FYI this issue was reproduced on s3 & Filesystem as well when using `great_expectations checkpoint run <checkpoint_name>`

Resolving patch:
diff --git a/great_expectations/render/renderer/site_builder.py b/great_expectations/render/renderer/site_builder.py
--- a/great_expectations/render/renderer/site_builder.py
+++ b/great_expectations/render/renderer/site_builder.py
@@ -386,14 +386,22 @@ def build(self, resource_identifiers=None):
                 source_store_keys, key=lambda x: x.run_id.run_time, reverse=True
             )[: self.validation_results_limit]
 
+        expectation_suite_identifier_exists: bool = any(
+            [isinstance(ri, ExpectationSuiteIdentifier) for ri in resource_identifiers]
+        ) if resource_identifiers is not None else False
+
         for resource_key in source_store_keys:
-            # if no resource_identifiers are passed, the section
-            # builder will build
-            # a page for every keys in its source store.
-            # if the caller did pass resource_identifiers, the section builder
-            # will build pages only for the specified resources
-            if resource_identifiers and resource_key not in resource_identifiers:
-                continue
+
+            # All expectation suites are always rendered unless resource_identifiers contains ExpectationSuiteIdentifier(s).
+            if expectation_suite_identifier_exists or (self.name != "expectations"):
+
+                # if no resource_identifiers are passed, the section
+                # builder will build
+                # a page for every keys in its source store.
+                # if the caller did pass resource_identifiers, the section builder
+                # will build pages only for the specified resources
+                if resource_identifiers and resource_key not in resource_identifiers:
+                    continue
 
             if self.run_name_filter:
                 if not resource_key_passes_run_name_filter(


Test patch:
diff --git a/tests/cli/test_checkpoint.py b/tests/cli/test_checkpoint.py
--- a/tests/cli/test_checkpoint.py
+++ b/tests/cli/test_checkpoint.py
@@ -608,7 +608,31 @@ def test_checkpoint_run_happy_path_with_successful_validation(
     assert result.exit_code == 0
     assert "Validation succeeded!" in stdout
 
-    assert mock_emit.call_count == 4
+    # Check to make sure data docs are built
+    assert os.path.isfile(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "index.html")
+    )
+    assert os.path.isfile(
+        os.path.join(
+            root_dir,
+            "uncommitted",
+            "data_docs",
+            "local_site",
+            "expectations",
+            "Titanic",
+            "warning.html",
+        )
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "validations")
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "expectations")
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "static")
+    )
+    assert mock_emit.call_count == 5
     usage_emits = mock_emit.call_args_list
     assert usage_emits[0] == mock.call(
         {"event_payload": {}, "event": "data_context.__init__", "success": True}
@@ -616,10 +640,13 @@ def test_checkpoint_run_happy_path_with_successful_validation(
     assert usage_emits[1][0][0]["event"] == "data_asset.validate"
     assert usage_emits[1][0][0]["success"] is True
 
-    assert usage_emits[2][0][0]["event"] == "data_context.run_validation_operator"
+    assert usage_emits[2][0][0]["event"] == "data_context.build_data_docs"
     assert usage_emits[2][0][0]["success"] is True
 
-    assert usage_emits[3] == mock.call(
+    assert usage_emits[3][0][0]["event"] == "data_context.run_validation_operator"
+    assert usage_emits[3][0][0]["success"] is True
+
+    assert usage_emits[4] == mock.call(
         {"event": "cli.checkpoint.run", "event_payload": {}, "success": True}
     )
 
@@ -650,7 +677,32 @@ def test_checkpoint_run_happy_path_with_failed_validation(
     assert result.exit_code == 1
     assert "Validation failed!" in stdout
 
-    assert mock_emit.call_count == 4
+    # Check to make sure data docs are built
+    assert os.path.isfile(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "index.html")
+    )
+    assert os.path.isfile(
+        os.path.join(
+            root_dir,
+            "uncommitted",
+            "data_docs",
+            "local_site",
+            "expectations",
+            "Titanic",
+            "warning.html",
+        )
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "validations")
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "expectations")
+    )
+    assert os.path.isdir(
+        os.path.join(root_dir, "uncommitted", "data_docs", "local_site", "static")
+    )
+
+    assert mock_emit.call_count == 5
     usage_emits = mock_emit.call_args_list
     assert usage_emits[0] == mock.call(
         {"event_payload": {}, "event": "data_context.__init__", "success": True}
@@ -658,10 +710,13 @@ def test_checkpoint_run_happy_path_with_failed_validation(
     assert usage_emits[1][0][0]["event"] == "data_asset.validate"
     assert usage_emits[1][0][0]["success"] is True
 
-    assert usage_emits[2][0][0]["event"] == "data_context.run_validation_operator"
+    assert usage_emits[2][0][0]["event"] == "data_context.build_data_docs"
     assert usage_emits[2][0][0]["success"] is True
 
-    assert usage_emits[3] == mock.call(
+    assert usage_emits[3][0][0]["event"] == "data_context.run_validation_operator"
+    assert usage_emits[3][0][0]["success"] is True
+
+    assert usage_emits[4] == mock.call(
         {"event": "cli.checkpoint.run", "event_payload": {}, "success": True}
     )
 
diff --git a/tests/render/test_page_renderer.py b/tests/render/test_page_renderer.py
--- a/tests/render/test_page_renderer.py
+++ b/tests/render/test_page_renderer.py
@@ -238,7 +238,8 @@ def test_ValidationResultsPageRenderer_render_validation_header(
             },
         },
     }
-    import pprint 
+    import pprint
+
     pprint.pprint(validation_header)
     assert validation_header == expected_validation_header
 
@@ -515,10 +516,11 @@ def test_snapshot_ValidationResultsPageRenderer_render_with_run_info_at_end(
         titanic_profiled_evrs_1
     ).to_json_dict()
     import pprint
-    pprint.pprint(rendered_validation_results['sections'])
-    with open(file_relative_path(__file__, "./fixtures/ValidationResultsPageRenderer_render_with_run_info_at_end_nc.json"), "w") as f:
-        json.dump(rendered_validation_results, f, indent=2)
-    # pprint.pprint(ValidationResultsPageRenderer_render_with_run_info_at_end)
+
+    pprint.pprint(rendered_validation_results["sections"])
+    # with open(file_relative_path(__file__, "./fixtures/ValidationResultsPageRenderer_render_with_run_info_at_end_nc.json"), "w") as f:
+    #     json.dump(rendered_validation_results, f, indent=2)
+    pprint.pprint(ValidationResultsPageRenderer_render_with_run_info_at_end)
     assert (
         rendered_validation_results
         == ValidationResultsPageRenderer_render_with_run_info_at_end
@@ -535,9 +537,9 @@ def test_snapshot_ValidationResultsPageRenderer_render_with_run_info_at_start(
     rendered_validation_results = validation_results_page_renderer.render(
         titanic_profiled_evrs_1
     ).to_json_dict()
-    # print(rendered_validation_results)
-    with open(file_relative_path(__file__, "./fixtures/ValidationResultsPageRenderer_render_with_run_info_at_start_nc.json"), "w") as f:
-        json.dump(rendered_validation_results, f, indent=2)
+    print(rendered_validation_results)
+    # with open(file_relative_path(__file__, "./fixtures/ValidationResultsPageRenderer_render_with_run_info_at_start_nc.json"), "w") as f:
+    #     json.dump(rendered_validation_results, f, indent=2)
 
     assert (
         rendered_validation_results
diff --git a/tests/test_fixtures/great_expectations_titanic.yml b/tests/test_fixtures/great_expectations_titanic.yml
--- a/tests/test_fixtures/great_expectations_titanic.yml
+++ b/tests/test_fixtures/great_expectations_titanic.yml
@@ -20,8 +20,6 @@ evaluation_parameter_store_name: evaluation_parameter_store
 validations_store_name: validations_store
 expectations_store_name: expectations_store
 
-data_docs_sites:
-
 stores:
   expectations_store:
     class_name: ExpectationsStore
@@ -52,6 +50,9 @@ validation_operators:
         action:
           class_name: StoreEvaluationParametersAction
           target_store_name: evaluation_parameter_store
+      - name: update_data_docs
+        action:
+          class_name: UpdateDataDocsAction
       # Uncomment the notify_slack action below to send notifications during evaluation
       # - name: notify_slack
       #   action:
@@ -61,3 +62,12 @@ validation_operators:
       #     renderer:
       #       module_name: great_expectations.render.renderer.slack_renderer
       #       class_name: SlackRenderer
+
+data_docs_sites:
+  local_site:  # this is a user-selected name - you may select your own
+    class_name: SiteBuilder
+    store_backend:
+      class_name: TupleFilesystemStoreBackend
+      base_directory: uncommitted/data_docs/local_site/ # this is the default path but can be changed as required
+    site_index_builder:
+      class_name: DefaultSiteIndexBuilder