Repository: PrefectHQ/prefect

Issue:
After deploying a flow, display a UI link to see it immediately
UX improvement: after calling `flow.deploy()`, present a message to the user with a link to viewing the flow in the UI.


Discussion:
I was actually thinking about this while I was writing https://github.com/dylanbhughes/reddit-daily. It would be great to have a link to the UI in Cloud, maybe in the flow context? For example, let's say I'm sending myself a slack message because one of my reference tasks failed, it would be great to include the link to the Cloud UI for the failed task run.
@dylanbhughes is the URL completely determined by the Flow run ID?  If so, context already has `flow_run_id`.
@cicdw It might be nice to have a `flow_run_cloud_url` string in context, though. So people don't have to construct it manually
Related to this: right now running from the CLI prints:
```
Flow Run ID: 036aec16-e184-4c83-afd4-6fa36447a249
```
Seems more useful to print a link directly to the run in the UI: https://cloud.prefect.io/replica/flow-run/036aec16-e184-4c83-afd4-6fa36447a249

Thoughts?
@bnaul Yeah that's exactly what we want! There's a few CLI enhancements coming down the pipe and this will probably be included
Yea that makes sense; the only thing that has me hesitant is that for all of this we'd need to make an additionally query for your tenant slug.  Obviously not a big deal, but it'd be nice to avoid making that.

Resolving patch:
diff --git a/src/prefect/cli/run.py b/src/prefect/cli/run.py
--- a/src/prefect/cli/run.py
+++ b/src/prefect/cli/run.py
@@ -1,9 +1,11 @@
 import json
+import re
 import time
 
 import click
 from tabulate import tabulate
 
+from prefect import config
 from prefect.client import Client
 from prefect.utilities.graphql import EnumValue, with_args
 
@@ -24,11 +26,11 @@ def run():
     \b
     Examples:
         $ prefect run cloud --name Test-Flow --project My-Project
-        Flow Run ID: 2ba3rrfd-411c-4d99-bb2a-f64a6dea78f9
+        Flow Run: https://cloud.prefect.io/myslug/flow-run/2ba3rrfd-411c-4d99-bb2a-f64a6dea78f9
 
     \b
         $ prefect run cloud --name Test-Flow --project My-Project --watch
-        Flow Run ID: 2ba3rrfd-411c-4d99-bb2a-f64a6dea78f9
+        Flow Run: https://cloud.prefect.io/myslug/flow-run/2ba3rrfd-411c-4d99-bb2a-f64a6dea78f9
         Scheduled -> Submitted -> Running -> Success
     """
 
@@ -66,8 +68,22 @@ def run():
 @click.option(
     "--logs", "-l", is_flag=True, help="Live logs of the flow run.", hidden=True
 )
+@click.option(
+    "--no-url",
+    is_flag=True,
+    help="Only output flow run id instead of link.",
+    hidden=True,
+)
 def cloud(
-    name, project, version, parameters_file, parameters_string, run_name, watch, logs
+    name,
+    project,
+    version,
+    parameters_file,
+    parameters_string,
+    run_name,
+    watch,
+    logs,
+    no_url,
 ):
     """
     Run a registered flow in Prefect Cloud.
@@ -82,6 +98,7 @@ def cloud(
         --run-name, -rn             TEXT        A name to assign for this run
         --watch, -w                             Watch current state of the flow run, stream output to stdout
         --logs, -l                              Get logs of the flow run, stream output to stdout
+        --no-url                                Only output the flow run id instead of a link
 
     \b
     If both `--parameters-file` and `--parameters-string` are provided then the values passed
@@ -147,7 +164,22 @@ def cloud(
     flow_run_id = client.create_flow_run(
         flow_id=flow_id, parameters={**file_params, **string_params}, run_name=run_name
     )
-    click.echo("Flow Run ID: {}".format(flow_run_id))
+
+    if no_url:
+        click.echo("Flow Run ID: {}".format(flow_run_id))
+    else:
+        # Generate direct link to Cloud run
+        tenant_slug = client.get_default_tenant_slug()
+
+        url = (
+            re.sub("api-", "", config.cloud.api)
+            if re.search("api-", config.cloud.api)
+            else re.sub("api", "cloud", config.cloud.api)
+        )
+
+        flow_run_url = "/".join([url.rstrip("/"), tenant_slug, "flow-run", flow_run_id])
+
+        click.echo("Flow Run: {}".format(flow_run_url))
 
     if watch:
         current_states = []
diff --git a/src/prefect/client/client.py b/src/prefect/client/client.py
--- a/src/prefect/client/client.py
+++ b/src/prefect/client/client.py
@@ -1,6 +1,7 @@
 import datetime
 import json
 import os
+import re
 import uuid
 import warnings
 from pathlib import Path
@@ -545,6 +546,7 @@ def register(
         set_schedule_active: bool = True,
         version_group_id: str = None,
         compressed: bool = True,
+        no_url: bool = False,
     ) -> str:
         """
         Push a new flow to Prefect Cloud
@@ -562,6 +564,8 @@ def register(
                 will be used.
             - compressed (bool, optional): if `True`, the serialized flow will be; defaults to `True`
                 compressed
+            - no_url (bool, optional): if `True`, the stdout from this function will not contain the
+                URL link to the newly-registered flow in the Cloud UI
 
         Returns:
             - str: the ID of the newly-registered flow
@@ -640,8 +644,37 @@ def register(
             if compressed
             else res.data.createFlow.id
         )
+
+        if not no_url:
+            # Generate direct link to Cloud flow
+            tenant_slug = self.get_default_tenant_slug()
+
+            url = (
+                re.sub("api-", "", prefect.config.cloud.api)
+                if re.search("api-", prefect.config.cloud.api)
+                else re.sub("api", "cloud", prefect.config.cloud.api)
+            )
+
+            flow_url = "/".join([url.rstrip("/"), tenant_slug, "flow", flow_id])
+
+            print("Flow: {}".format(flow_url))
+
         return flow_id
 
+    def get_default_tenant_slug(self) -> str:
+        """
+        Get the default tenant slug for the currently authenticated user
+
+        Returns:
+            - str: the slug of the current default tenant for this user
+        """
+        res = self.graphql(
+            query={"query": {"user": {"default_membership": {"tenant": "slug"}}}}
+        )
+
+        user = res.get("data").user[0]
+        return user.default_membership.tenant.slug
+
     def create_project(self, project_name: str, project_description: str = None) -> str:
         """
         Create a new Project
diff --git a/src/prefect/core/flow.py b/src/prefect/core/flow.py
--- a/src/prefect/core/flow.py
+++ b/src/prefect/core/flow.py
@@ -1362,6 +1362,7 @@ def register(
         labels: List[str] = None,
         set_schedule_active: bool = True,
         version_group_id: str = None,
+        no_url: bool = False,
         **kwargs: Any
     ) -> str:
         """
@@ -1380,6 +1381,8 @@ def register(
             - version_group_id (str, optional): the UUID version group ID to use for versioning this Flow
                 in Cloud; if not provided, the version group ID associated with this Flow's project and name
                 will be used.
+            - no_url (bool, optional): if `True`, the stdout from this function will not contain the
+                URL link to the newly-registered flow in the Cloud UI
             - **kwargs (Any): if instantiating a Storage object from default settings, these keyword arguments
                 will be passed to the initialization method of the default Storage class
 
@@ -1406,6 +1409,7 @@ def register(
             project_name=project_name,
             set_schedule_active=set_schedule_active,
             version_group_id=version_group_id,
+            no_url=no_url,
         )
         return registered_flow
 


Test patch:
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
--- a/tests/cli/test_run.py
+++ b/tests/cli/test_run.py
@@ -34,27 +34,29 @@ def test_run_help():
 def test_run_cloud(monkeypatch):
     post = MagicMock(
         return_value=MagicMock(
-            json=MagicMock(return_value=dict(data=dict(flow=[{"id": "flow"}])))
+            json=MagicMock(return_value=dict(data=dict(flow=[{"id": "flow"}],)))
         )
     )
     session = MagicMock()
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    create_flow_run = MagicMock(return_value="id")
     monkeypatch.setattr(
-        "prefect.client.Client.create_flow_run", MagicMock(return_value=create_flow_run)
+        "prefect.client.Client.create_flow_run", MagicMock(return_value="id")
+    )
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
     )
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
             run, ["cloud", "--name", "flow", "--project", "project", "--version", "2"]
         )
         assert result.exit_code == 0
-        assert "Flow Run ID" in result.output
+        assert "Flow Run" in result.output
 
         query = """
         query {
@@ -90,13 +92,15 @@ def test_run_cloud_watch(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    create_flow_run = MagicMock(return_value="id")
     monkeypatch.setattr(
-        "prefect.client.Client.create_flow_run", MagicMock(return_value=create_flow_run)
+        "prefect.client.Client.create_flow_run", MagicMock(return_value="id")
+    )
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
     )
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -146,13 +150,15 @@ def test_run_cloud_logs(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    create_flow_run = MagicMock(return_value="id")
     monkeypatch.setattr(
-        "prefect.client.Client.create_flow_run", MagicMock(return_value=create_flow_run)
+        "prefect.client.Client.create_flow_run", MagicMock(return_value="id")
+    )
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
     )
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -184,7 +190,7 @@ def test_run_cloud_fails(monkeypatch):
     monkeypatch.setattr("requests.Session", session)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -196,7 +202,7 @@ def test_run_cloud_fails(monkeypatch):
 
 def test_run_cloud_no_param_file(monkeypatch):
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -230,9 +236,11 @@ def test_run_cloud_param_file(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    mock_client = MagicMock()
-    mock_client.create_flow_run.return_value = "id"
-    monkeypatch.setattr("prefect.cli.run.Client", MagicMock(return_value=mock_client))
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
 
     with tempfile.TemporaryDirectory() as directory:
         file_path = os.path.join(directory, "file.json")
@@ -240,7 +248,7 @@ def test_run_cloud_param_file(monkeypatch):
             json.dump({"test": 42}, tmp)
 
         with set_temporary_config(
-            {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+            {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
         ):
             runner = CliRunner()
             result = runner.invoke(
@@ -258,11 +266,9 @@ def test_run_cloud_param_file(monkeypatch):
                 ],
             )
             assert result.exit_code == 0
-            assert "Flow Run ID" in result.output
-            assert mock_client.create_flow_run.called
-            assert mock_client.create_flow_run.call_args[1]["parameters"] == {
-                "test": 42
-            }
+            assert "Flow Run" in result.output
+            assert create_flow_run_mock.called
+            assert create_flow_run_mock.call_args[1]["parameters"] == {"test": 42}
 
 
 def test_run_cloud_param_string(monkeypatch):
@@ -275,12 +281,14 @@ def test_run_cloud_param_string(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    mock_client = MagicMock()
-    mock_client.create_flow_run.return_value = "id"
-    monkeypatch.setattr("prefect.cli.run.Client", MagicMock(return_value=mock_client))
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -298,9 +306,9 @@ def test_run_cloud_param_string(monkeypatch):
             ],
         )
         assert result.exit_code == 0
-        assert "Flow Run ID" in result.output
-        assert mock_client.create_flow_run.called
-        assert mock_client.create_flow_run.call_args[1]["parameters"] == {"test": 42}
+        assert "Flow Run" in result.output
+        assert create_flow_run_mock.called
+        assert create_flow_run_mock.call_args[1]["parameters"] == {"test": 42}
 
 
 def test_run_cloud_run_name(monkeypatch):
@@ -313,12 +321,14 @@ def test_run_cloud_run_name(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    mock_client = MagicMock()
-    mock_client.create_flow_run.return_value = "id"
-    monkeypatch.setattr("prefect.cli.run.Client", MagicMock(return_value=mock_client))
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         runner = CliRunner()
         result = runner.invoke(
@@ -336,9 +346,9 @@ def test_run_cloud_run_name(monkeypatch):
             ],
         )
         assert result.exit_code == 0
-        assert "Flow Run ID" in result.output
-        assert mock_client.create_flow_run.called
-        assert mock_client.create_flow_run.call_args[1]["run_name"] == "NAME"
+        assert "Flow Run" in result.output
+        assert create_flow_run_mock.called
+        assert create_flow_run_mock.call_args[1]["run_name"] == "NAME"
 
 
 def test_run_cloud_param_string_overwrites(monkeypatch):
@@ -351,9 +361,11 @@ def test_run_cloud_param_string_overwrites(monkeypatch):
     session.return_value.post = post
     monkeypatch.setattr("requests.Session", session)
 
-    mock_client = MagicMock()
-    mock_client.create_flow_run.return_value = "id"
-    monkeypatch.setattr("prefect.cli.run.Client", MagicMock(return_value=mock_client))
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
 
     with tempfile.TemporaryDirectory() as directory:
         file_path = os.path.join(directory, "file.json")
@@ -361,7 +373,7 @@ def test_run_cloud_param_string_overwrites(monkeypatch):
             json.dump({"test": 42}, tmp)
 
         with set_temporary_config(
-            {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+            {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
         ):
             runner = CliRunner()
             result = runner.invoke(
@@ -381,8 +393,71 @@ def test_run_cloud_param_string_overwrites(monkeypatch):
                 ],
             )
             assert result.exit_code == 0
-            assert "Flow Run ID" in result.output
-            assert mock_client.create_flow_run.called
-            assert mock_client.create_flow_run.call_args[1]["parameters"] == {
-                "test": 43
-            }
+            assert "Flow Run" in result.output
+            assert create_flow_run_mock.called
+            assert create_flow_run_mock.call_args[1]["parameters"] == {"test": 43}
+
+
+def test_run_cloud_flow_run_id_link(monkeypatch):
+    post = MagicMock(
+        return_value=MagicMock(
+            json=MagicMock(return_value=dict(data=dict(flow=[{"id": "flow"}])))
+        )
+    )
+    session = MagicMock()
+    session.return_value.post = post
+    monkeypatch.setattr("requests.Session", session)
+
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
+    with set_temporary_config(
+        {"cloud.api": "https://api.foo", "cloud.auth_token": "secret_token"}
+    ):
+        runner = CliRunner()
+        result = runner.invoke(
+            run, ["cloud", "--name", "flow", "--project", "project", "--version", "2",],
+        )
+        assert result.exit_code == 0
+        assert "Flow Run" in result.output
+        assert "https://cloud.foo/tslug/flow-run/id" in result.output
+
+
+def test_run_cloud_flow_run_id_no_link(monkeypatch):
+    post = MagicMock(
+        return_value=MagicMock(
+            json=MagicMock(return_value=dict(data=dict(flow=[{"id": "flow"}])))
+        )
+    )
+    session = MagicMock()
+    session.return_value.post = post
+    monkeypatch.setattr("requests.Session", session)
+
+    create_flow_run_mock = MagicMock(return_value="id")
+    monkeypatch.setattr("prefect.client.Client.create_flow_run", create_flow_run_mock)
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
+    with set_temporary_config(
+        {"cloud.api": "https://api.foo", "cloud.auth_token": "secret_token"}
+    ):
+        runner = CliRunner()
+        result = runner.invoke(
+            run,
+            [
+                "cloud",
+                "--name",
+                "flow",
+                "--project",
+                "project",
+                "--version",
+                "2",
+                "--no-url",
+            ],
+        )
+        assert result.exit_code == 0
+        assert "Flow Run ID" in result.output
diff --git a/tests/client/test_client.py b/tests/client/test_client.py
--- a/tests/client/test_client.py
+++ b/tests/client/test_client.py
@@ -38,7 +38,7 @@ def test_version_header(monkeypatch):
     session.return_value.get = get
     monkeypatch.setattr("requests.Session", session)
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     client.get("/foo/bar")
@@ -53,7 +53,7 @@ def test_version_header_cant_be_overridden(monkeypatch):
     session.return_value.get = get
     monkeypatch.setattr("requests.Session", session)
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     client.get("/foo/bar", headers={"X-PREFECT-CORE-VERSION": "-1"})
@@ -80,7 +80,7 @@ def test_graphql_errors_get_raised(patch_post):
     patch_post(dict(data="42", errors="GraphQL issue!"))
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     with pytest.raises(ClientError, match="GraphQL issue!"):
@@ -88,7 +88,7 @@ def test_graphql_errors_get_raised(patch_post):
 
 
 @pytest.mark.parametrize("compressed", [True, False])
-def test_client_deploy(patch_post, compressed):
+def test_client_deploy(patch_post, compressed, monkeypatch):
     if compressed:
         response = {
             "data": {
@@ -102,8 +102,12 @@ def test_client_deploy(patch_post, compressed):
         }
     patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
@@ -120,7 +124,7 @@ def test_client_deploy(patch_post, compressed):
 
 
 @pytest.mark.parametrize("compressed", [True, False])
-def test_client_register(patch_post, compressed):
+def test_client_register(patch_post, compressed, monkeypatch):
     if compressed:
         response = {
             "data": {
@@ -134,8 +138,12 @@ def test_client_register(patch_post, compressed):
         }
     patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
@@ -152,7 +160,7 @@ def test_client_register(patch_post, compressed):
 
 @pytest.mark.parametrize("compressed", [True, False])
 def test_client_register_raises_for_keyed_flows_with_no_result_handler(
-    patch_post, compressed
+    patch_post, compressed, monkeypatch
 ):
     if compressed:
         response = {
@@ -167,12 +175,16 @@ def test_client_register_raises_for_keyed_flows_with_no_result_handler(
         }
     patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     @prefect.task
     def a(x):
         pass
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     with prefect.Flow(
@@ -192,7 +204,9 @@ def a(x):
 
 
 @pytest.mark.parametrize("compressed", [True, False])
-def test_client_register_doesnt_raise_if_no_keyed_edges(patch_post, compressed):
+def test_client_register_doesnt_raise_if_no_keyed_edges(
+    patch_post, compressed, monkeypatch
+):
     if compressed:
         response = {
             "data": {
@@ -206,8 +220,12 @@ def test_client_register_doesnt_raise_if_no_keyed_edges(patch_post, compressed):
         }
     patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
@@ -223,7 +241,7 @@ def test_client_register_doesnt_raise_if_no_keyed_edges(patch_post, compressed):
 
 
 @pytest.mark.parametrize("compressed", [True, False])
-def test_client_register_builds_flow(patch_post, compressed):
+def test_client_register_builds_flow(patch_post, compressed, monkeypatch):
     if compressed:
         response = {
             "data": {
@@ -237,8 +255,12 @@ def test_client_register_builds_flow(patch_post, compressed):
         }
     post = patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
@@ -263,7 +285,9 @@ def test_client_register_builds_flow(patch_post, compressed):
 
 
 @pytest.mark.parametrize("compressed", [True, False])
-def test_client_register_optionally_avoids_building_flow(patch_post, compressed):
+def test_client_register_optionally_avoids_building_flow(
+    patch_post, compressed, monkeypatch
+):
     if compressed:
         response = {
             "data": {
@@ -277,8 +301,12 @@ def test_client_register_optionally_avoids_building_flow(patch_post, compressed)
         }
     post = patch_post(response)
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test")
@@ -302,11 +330,15 @@ def test_client_register_optionally_avoids_building_flow(patch_post, compressed)
     assert serialized_flow["storage"] is None
 
 
-def test_client_register_with_bad_proj_name(patch_post):
+def test_client_register_with_bad_proj_name(patch_post, monkeypatch):
     patch_post({"data": {"project": []}})
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     flow = prefect.Flow(name="test")
@@ -318,11 +350,15 @@ def test_client_register_with_bad_proj_name(patch_post):
     assert "client.create_project" in str(exc.value)
 
 
-def test_client_register_with_flow_that_cant_be_deserialized(patch_post):
+def test_client_register_with_flow_that_cant_be_deserialized(patch_post, monkeypatch):
     patch_post({"data": {"project": [{"id": "proj-id"}]}})
 
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
@@ -342,6 +378,83 @@ def test_client_register_with_flow_that_cant_be_deserialized(patch_post):
         client.register(flow, project_name="my-default-project", build=False)
 
 
+@pytest.mark.parametrize("compressed", [True, False])
+def test_client_register_flow_id_output(patch_post, compressed, monkeypatch, capsys):
+    if compressed:
+        response = {
+            "data": {
+                "project": [{"id": "proj-id"}],
+                "createFlowFromCompressedString": {"id": "long-id"},
+            }
+        }
+    else:
+        response = {
+            "data": {"project": [{"id": "proj-id"}], "createFlow": {"id": "long-id"}}
+        }
+    patch_post(response)
+
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
+    with set_temporary_config(
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+    ):
+        client = Client()
+    flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
+    flow.result_handler = flow.storage.result_handler
+
+    flow_id = client.register(
+        flow,
+        project_name="my-default-project",
+        compressed=compressed,
+        version_group_id=str(uuid.uuid4()),
+    )
+    assert flow_id == "long-id"
+
+    captured = capsys.readouterr()
+    assert captured.out == "Flow: https://cloud.prefect.io/tslug/flow/long-id\n"
+
+
+@pytest.mark.parametrize("compressed", [True, False])
+def test_client_register_flow_id_no_output(patch_post, compressed, monkeypatch, capsys):
+    if compressed:
+        response = {
+            "data": {
+                "project": [{"id": "proj-id"}],
+                "createFlowFromCompressedString": {"id": "long-id"},
+            }
+        }
+    else:
+        response = {
+            "data": {"project": [{"id": "proj-id"}], "createFlow": {"id": "long-id"}}
+        }
+    patch_post(response)
+
+    monkeypatch.setattr(
+        "prefect.client.Client.get_default_tenant_slug", MagicMock(return_value="tslug")
+    )
+
+    with set_temporary_config(
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+    ):
+        client = Client()
+    flow = prefect.Flow(name="test", storage=prefect.environments.storage.Memory())
+    flow.result_handler = flow.storage.result_handler
+
+    flow_id = client.register(
+        flow,
+        project_name="my-default-project",
+        compressed=compressed,
+        version_group_id=str(uuid.uuid4()),
+        no_url=True,
+    )
+    assert flow_id == "long-id"
+
+    captured = capsys.readouterr()
+    assert captured.out == ""
+
+
 def test_get_flow_run_info(patch_post):
     response = {
         "flow_run_by_pk": {
@@ -385,7 +498,7 @@ def test_get_flow_run_info(patch_post):
     post = patch_post(dict(data=response))
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.get_flow_run_info(flow_run_id="74-salt")
@@ -444,7 +557,7 @@ def test_get_flow_run_info_with_nontrivial_payloads(patch_post):
     post = patch_post(dict(data=response))
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.get_flow_run_info(flow_run_id="74-salt")
@@ -467,7 +580,7 @@ def test_get_flow_run_info_with_nontrivial_payloads(patch_post):
 def test_get_flow_run_info_raises_informative_error(patch_post):
     post = patch_post(dict(data={"flow_run_by_pk": None}))
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     with pytest.raises(ClientError, match="not found"):
@@ -479,7 +592,7 @@ def test_set_flow_run_state(patch_post):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.set_flow_run_state(
@@ -496,7 +609,7 @@ def test_set_flow_run_state_with_error(patch_post):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     with pytest.raises(ClientError, match="something went wrong"):
@@ -527,7 +640,7 @@ def test_get_task_run_info(patch_post):
 
     post = patch_post(dict(data=response))
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.get_task_run_info(
@@ -549,7 +662,7 @@ def test_get_task_run_info_with_error(patch_post):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
@@ -565,7 +678,7 @@ def test_set_task_run_state(patch_post):
     state = Pending()
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.set_task_run_state(task_run_id="76-salt", version=0, state=state)
@@ -579,7 +692,7 @@ def test_set_task_run_state_responds_to_status(patch_post):
     state = Pending()
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
     result = client.set_task_run_state(task_run_id="76-salt", version=0, state=state)
@@ -599,7 +712,7 @@ def test_set_task_run_state_responds_to_config_when_queued(patch_post):
 
     with set_temporary_config(
         {
-            "cloud.graphql": "http://my-cloud.foo",
+            "cloud.api": "http://my-cloud.foo",
             "cloud.auth_token": "secret_token",
             "cloud.queue_interval": 750,
         }
@@ -620,7 +733,7 @@ def test_set_task_run_state_serializes(patch_post):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
@@ -639,7 +752,7 @@ def test_set_task_run_state_with_error(patch_post):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
@@ -649,7 +762,7 @@ def test_set_task_run_state_with_error(patch_post):
 
 def test_create_flow_run_requires_flow_id_or_version_group_id():
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
@@ -667,7 +780,7 @@ def test_create_flow_run_with_input(patch_post, kwargs):
     post = patch_post(response)
 
     with set_temporary_config(
-        {"cloud.graphql": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
+        {"cloud.api": "http://my-cloud.foo", "cloud.auth_token": "secret_token"}
     ):
         client = Client()
 
diff --git a/tests/core/test_flow.py b/tests/core/test_flow.py
--- a/tests/core/test_flow.py
+++ b/tests/core/test_flow.py
@@ -2254,7 +2254,11 @@ def test_flow_register_passes_kwargs_to_storage(self, monkeypatch):
             }
         ):
             f.register(
-                "My-project", registry_url="FOO", image_name="BAR", image_tag="BIG"
+                "My-project",
+                registry_url="FOO",
+                image_name="BAR",
+                image_tag="BIG",
+                no_url=True,
             )
 
         assert isinstance(f.storage, prefect.environments.storage.Docker)