Repository: voxel51/fiftyone

Issue:
[FR] Local storage not working for colab notebooks
Unsure if anything can easily be done about this, but, the `Want a free t-shirt?` button comes back every time I do `session.show()` in Colab notebooks. 

Conversely, in Jupyter notebooks, I observe that FiftyOne remembers whether I have dismissed the `Want a free t-shirt?` button (due to browser local storage, as in non-notebook browser environments, I believe).

<img width="1220" alt="Screen Shot 2020-12-22 at 12 02 38 PM" src="https://user-images.githubusercontent.com/25985824/102913996-9fda8180-444d-11eb-9b83-5f19578b1c73.png">



Discussion:

Resolving patch:
diff --git a/fiftyone/constants.py b/fiftyone/constants.py
--- a/fiftyone/constants.py
+++ b/fiftyone/constants.py
@@ -18,6 +18,7 @@
 FIFTYONE_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".fiftyone")
 FIFTYONE_CONFIG_PATH = os.path.join(FIFTYONE_CONFIG_DIR, "config.json")
 BASE_DIR = os.path.dirname(FIFTYONE_DIR)
+FEEDBACK_PATH = os.path.join(FIFTYONE_CONFIG_DIR, "var", "feedback.json")
 RESOURCES_DIR = os.path.join(FIFTYONE_DIR, "resources")
 
 # Package metadata
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -23,6 +23,7 @@
 import tornado.websocket
 
 import eta.core.labels as etal
+import eta.core.serial as etas
 import eta.core.utils as etau
 import eta.core.video as etav
 
@@ -70,8 +71,7 @@ def set_default_headers(self, *args, **kwargs):
     async def get(self):
         self.write(self.get_response())
 
-    @staticmethod
-    def get_response():
+    def get_response(self):
         """Returns the serializable response
 
         Returns:
@@ -91,10 +91,16 @@ def get_response():
             dict
         """
         uid, first_import = _get_user_id()
+        isfile = os.path.isfile(foc.FEEDBACK_PATH)
+        if isfile:
+            submitted = etas.load_json(foc.FEEDBACK_PATH)["submitted"]
+        else:
+            submitted = False
         return {
             "version": foc.VERSION,
             "user_id": uid,
             "do_not_track": fo.config.do_not_track,
+            "feedback": {"submitted": submitted, "minimized": isfile},
             "dev_install": foc.DEV_INSTALL or foc.RC_INSTALL,
         }
 
@@ -117,6 +123,14 @@ def get_response():
         }
 
 
+class FeedbackHandler(RequestHandler):
+    """Returns whether the feedback button should be minimized"""
+
+    def post(self):
+        submitted = self.get_argument("submitted", False)
+        etas.write_json({"submitted": submitted}, foc.FEEDBACK_PATH)
+
+
 def _catch_errors(func):
     async def wrapper(self, *args, **kwargs):
         try:
@@ -980,6 +994,7 @@ def __init__(self, **settings):
         handlers = [
             (r"/fiftyone", FiftyOneHandler),
             (r"/polling", PollingHandler),
+            (r"/feedback", FeedbackHandler),
             (r"/filepath/(.*)", FileHandler, {"path": static_path},),
             (r"/stages", StagesHandler),
             (r"/state", StateHandler),
diff --git a/fiftyone/utils/uid.py b/fiftyone/utils/uid.py
--- a/fiftyone/utils/uid.py
+++ b/fiftyone/utils/uid.py
@@ -9,6 +9,8 @@
 
 import fiftyone.constants as foc
 
+_FIRST_IMPORT = "FIFTYONE_FIRST_IMPORT"
+
 
 def _get_user_id():
     """Gets the UUID of the current user
@@ -25,10 +27,16 @@ def read():
         except (IOError, StopIteration):
             return None
 
+    first_import = False
+    if _FIRST_IMPORT in os.environ:
+        first_import = os.environ[_FIRST_IMPORT] == "1"
+
     if not read():
+        os.environ[_FIRST_IMPORT] = "1"
         os.makedirs(os.path.dirname(uid_path), exist_ok=True)
         with open(uid_path, "w") as f:
             f.write(str(uuid.uuid4()))
 
         return read(), True
-    return read(), False
+
+    return read(), first_import


Test patch: