Repository: PrefectHQ/prefect

Issue:
missing arg LATE on cli
## Description
prefect==2.0b13
`LATE` is not possible as an option for `--state-type` param on the prefect cli.


## Reproduction / Example
```bash
❯ prefect flow-run ls --help
                                                                                                                   
 Usage: prefect flow-run ls [OPTIONS]                                                                              
                                                                                                                   
 View recent flow runs or flow runs for specific flows                                                             
                                                                                                                   
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --flow-name         TEXT                                          [default: None]                               │
│ --limit             INTEGER                                       [default: 15]                                 │
│ --state-type        [SCHEDULED|PENDING|RUNNING|COMPLETED|FAILED|  [default: None]                               │
│                     CANCELLED|CRASHED]                                                                          │
│ --help                                                            Show this message and exit.                   │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```


Discussion:
Hi @mikkelduif! `LATE` is an instance/sub-type of `SCHEDULED` so passing `SCHEDULED` would show you all of those flow runs, but it would also show you all of the other `SCHEDULED` ones that weren't late. The UI does allow this sort of filtering via the state name, but we should be able to include that same sort of filtering in the CLI as well.

Resolving patch:
diff --git a/src/prefect/cli/flow_run.py b/src/prefect/cli/flow_run.py
--- a/src/prefect/cli/flow_run.py
+++ b/src/prefect/cli/flow_run.py
@@ -6,6 +6,7 @@
 
 import httpx
 import pendulum
+import typer
 from fastapi import status
 from rich.pretty import Pretty
 from rich.table import Table
@@ -44,21 +45,27 @@ async def inspect(id: UUID):
 
 @flow_run_app.command()
 async def ls(
-    flow_name: List[str] = None,
-    limit: int = 15,
-    state_type: List[StateType] = None,
+    flow_name: List[str] = typer.Option(None, help="Name of the flow"),
+    limit: int = typer.Option(15, help="Maximum number of flow runs to list"),
+    state: List[str] = typer.Option(None, help="Name of the flow run's state"),
+    state_type: List[StateType] = typer.Option(
+        None, help="Type of the flow run's state"
+    ),
 ):
     """
     View recent flow runs or flow runs for specific flows
     """
+
+    state_filter = {}
+    if state:
+        state_filter["name"] = {"any_": state}
+    if state_type:
+        state_filter["type"] = {"any_": state_type}
+
     async with get_client() as client:
         flow_runs = await client.read_flow_runs(
             flow_filter=FlowFilter(name={"any_": flow_name}) if flow_name else None,
-            flow_run_filter=(
-                FlowRunFilter(state={"type": {"any_": state_type}})
-                if state_type
-                else None
-            ),
+            flow_run_filter=FlowRunFilter(state=state_filter) if state_filter else None,
             limit=limit,
             sort=FlowRunSort.EXPECTED_START_TIME_DESC,
         )


Test patch:
diff --git a/tests/cli/test_flow_run.py b/tests/cli/test_flow_run.py
--- a/tests/cli/test_flow_run.py
+++ b/tests/cli/test_flow_run.py
@@ -3,10 +3,22 @@
 import pytest
 
 import prefect.exceptions
+from prefect import flow
+from prefect.orion.schemas import states
 from prefect.testing.cli import invoke_and_assert
 from prefect.utilities.asyncutils import sync_compatible
 
 
+@flow(name="hello")
+def hello_flow():
+    return "Hello!"
+
+
+@flow(name="goodbye")
+def goodbye_flow():
+    return "Goodbye"
+
+
 @sync_compatible
 async def assert_flow_run_is_deleted(orion_client, flow_run_id: UUID):
     """
@@ -16,6 +28,51 @@ async def assert_flow_run_is_deleted(orion_client, flow_run_id: UUID):
         await orion_client.read_flow_run(flow_run_id)
 
 
+def assert_flow_runs_in_result(result, expected, unexpected=None):
+    output = result.stdout.strip()
+
+    # When running in tests the output of the columns in the table are
+    # truncated, so this only asserts that the first 20 characters of each
+    # flow run's id is in the output.
+
+    for flow_run in expected:
+        id_start = str(flow_run.id)[:20]
+        assert id_start in output
+
+    if unexpected:
+        for flow_run in unexpected:
+            id_start = str(flow_run.id)[:20]
+            assert id_start not in output, f"{flow_run} should not be in the output"
+
+
+@pytest.fixture
+async def scheduled_flow_run(orion_client):
+    return await orion_client.create_flow_run(
+        name="scheduled_flow_run", flow=hello_flow, state=states.Scheduled()
+    )
+
+
+@pytest.fixture
+async def completed_flow_run(orion_client):
+    return await orion_client.create_flow_run(
+        name="completed_flow_run", flow=hello_flow, state=states.Completed()
+    )
+
+
+@pytest.fixture
+async def running_flow_run(orion_client):
+    return await orion_client.create_flow_run(
+        name="running_flow_run", flow=goodbye_flow, state=states.Running()
+    )
+
+
+@pytest.fixture
+async def late_flow_run(orion_client):
+    return await orion_client.create_flow_run(
+        name="late_flow_run", flow=goodbye_flow, state=states.Late()
+    )
+
+
 def test_delete_flow_run_fails_correctly():
     missing_flow_run_id = "ccb86ed0-e824-4d8b-b825-880401320e41"
     invoke_and_assert(
@@ -33,3 +90,113 @@ def test_delete_flow_run_succeeds(orion_client, flow_run):
     )
 
     assert_flow_run_is_deleted(orion_client, flow_run.id)
+
+
+def test_ls_no_args(
+    scheduled_flow_run,
+    completed_flow_run,
+    running_flow_run,
+    late_flow_run,
+):
+    result = invoke_and_assert(
+        command=["flow-run", "ls"],
+        expected_code=0,
+    )
+
+    assert_flow_runs_in_result(
+        result,
+        [
+            scheduled_flow_run,
+            completed_flow_run,
+            running_flow_run,
+            late_flow_run,
+        ],
+    )
+
+
+def test_ls_flow_name_filter(
+    scheduled_flow_run,
+    completed_flow_run,
+    running_flow_run,
+    late_flow_run,
+):
+    result = invoke_and_assert(
+        command=["flow-run", "ls", "--flow-name", "goodbye"],
+        expected_code=0,
+    )
+
+    assert_flow_runs_in_result(
+        result,
+        expected=[running_flow_run, late_flow_run],
+        unexpected=[scheduled_flow_run, completed_flow_run],
+    )
+
+
+def test_ls_state_type_filter(
+    scheduled_flow_run,
+    completed_flow_run,
+    running_flow_run,
+    late_flow_run,
+):
+    result = invoke_and_assert(
+        command=[
+            "flow-run",
+            "ls",
+            "--state-type",
+            "COMPLETED",
+            "--state-type",
+            "RUNNING",
+        ],
+        expected_code=0,
+    )
+
+    assert_flow_runs_in_result(
+        result,
+        expected=[running_flow_run, completed_flow_run],
+        unexpected=[scheduled_flow_run, late_flow_run],
+    )
+
+
+def test_ls_state_name_filter(
+    scheduled_flow_run,
+    completed_flow_run,
+    running_flow_run,
+    late_flow_run,
+):
+    result = invoke_and_assert(
+        command=["flow-run", "ls", "--state", "Late"],
+        expected_code=0,
+    )
+
+    assert_flow_runs_in_result(
+        result,
+        expected=[late_flow_run],
+        unexpected=[running_flow_run, scheduled_flow_run, completed_flow_run],
+    )
+
+
+def test_ls_limit(
+    scheduled_flow_run,
+    completed_flow_run,
+    running_flow_run,
+    late_flow_run,
+):
+    result = invoke_and_assert(
+        command=["flow-run", "ls", "--limit", "2"],
+        expected_code=0,
+    )
+
+    output = result.stdout.strip()
+
+    found_count = 0
+    for flow_run in [
+        scheduled_flow_run,
+        completed_flow_run,
+        running_flow_run,
+        late_flow_run,
+    ]:
+        id_start = str(flow_run.id)[:20]
+        if id_start in output:
+            found_count += 1
+
+    assert found_count == 2