Repository: great-expectations/great_expectations

Issue:
data docs link has multiple slashes
In version 0.10.0, there are two slashes before 'index.html' which make link invalid, at least on Safari. Replacing the double slash with a single slash fixes the issue so this appears to be a simple typo in the code


Discussion:
@tejsvirai can you show me an example URL so I can fix this stat?
It would be helpful to know what store backend you are using too.
example URL: https://s3-us-west-2.amazonaws.com/kh-greatexpectations.net/DSVC/prod//index.html

backend: S3
Thanks! I'll ship a fix on Monday!

Resolving patch:
diff --git a/great_expectations/data_context/store/tuple_store_backend.py b/great_expectations/data_context/store/tuple_store_backend.py
--- a/great_expectations/data_context/store/tuple_store_backend.py
+++ b/great_expectations/data_context/store/tuple_store_backend.py
@@ -380,7 +380,9 @@ def get_url_for_key(self, key, protocol=None):
         else:
             location = "s3-" + location
         s3_key = self._convert_key_to_filepath(key)
-        return "https://%s.amazonaws.com/%s/%s/%s" % (location, self.bucket, self.prefix, s3_key)
+        if not self.prefix:
+            return f"https://{location}.amazonaws.com/{self.bucket}/{s3_key}"
+        return f"https://{location}.amazonaws.com/{self.bucket}/{self.prefix}/{s3_key}"
 
     def _has_key(self, key):
         all_keys = self.list_keys()


Test patch:
diff --git a/tests/data_context/store/test_store_backends.py b/tests/data_context/store/test_store_backends.py
--- a/tests/data_context/store/test_store_backends.py
+++ b/tests/data_context/store/test_store_backends.py
@@ -164,7 +164,7 @@ def test_TupleFilesystemStoreBackend_ignores_jupyter_notebook_checkpoints(tmp_pa
 
 
 @mock_s3
-def test_TupleS3StoreBackend():
+def test_TupleS3StoreBackend_with_prefix():
     """
     What does this test test and why?
 
@@ -209,6 +209,55 @@ def test_TupleS3StoreBackend():
         'this_is_a_test_prefix/my_file_AAA', 'this_is_a_test_prefix/my_file_BBB'}
     
     assert my_store.get_url_for_key(('AAA',)) == 'https://s3.amazonaws.com/%s/%s/my_file_AAA' % (bucket, prefix)
+    assert my_store.get_url_for_key(('BBB',)) == 'https://s3.amazonaws.com/%s/%s/my_file_BBB' % (bucket, prefix)
+
+
+@mock_s3
+def test_TupleS3StoreBackend_with_empty_prefixes():
+    """
+    What does this test test and why?
+
+    We will exercise the store backend's set method twice and then verify
+    that the we calling get and list methods will return the expected keys.
+
+    We will also check that the objects are stored on S3 at the expected location,
+    and that the correct S3 URL for the object can be retrieved.
+    """
+    bucket = "leakybucket"
+    prefix = ""
+
+    # create a bucket in Moto's mock AWS environment
+    conn = boto3.resource('s3', region_name='us-east-1')
+    conn.create_bucket(Bucket=bucket)
+
+    my_store = TupleS3StoreBackend(
+        filepath_template="my_file_{0}",
+        bucket=bucket,
+        prefix=prefix,
+    )
+
+    # We should be able to list keys, even when empty
+    keys = my_store.list_keys()
+    assert len(keys) == 0
+
+    my_store.set(("AAA",), "aaa", content_type='text/html; charset=utf-8')
+    assert my_store.get(("AAA",)) == 'aaa'
+
+    obj = boto3.client('s3').get_object(Bucket=bucket, Key=prefix + "/my_file_AAA")
+    assert obj["ContentType"] == 'text/html; charset=utf-8'
+    assert obj["ContentEncoding"] == 'utf-8'
+
+    my_store.set(("BBB",), "bbb")
+    assert my_store.get(("BBB",)) == 'bbb'
+
+    assert set(my_store.list_keys()) == {("AAA",), ("BBB",)}
+    assert set(
+        [s3_object_info['Key'] for s3_object_info in
+         boto3.client('s3').list_objects(Bucket=bucket, Prefix=prefix)['Contents']]) == {
+        'my_file_AAA', 'my_file_BBB'}
+
+    assert my_store.get_url_for_key(('AAA',)) == 'https://s3.amazonaws.com/leakybucket/my_file_AAA'
+    assert my_store.get_url_for_key(('BBB',)) == 'https://s3.amazonaws.com/leakybucket/my_file_BBB'
 
 
 def test_TupleGCSStoreBackend():