Repository: PrefectHQ/prefect
Pull request: Allow case-insensitive state and state type handling when listing flow runs via CLI
### Examples
#### Improving `--help` display for `prefect flow-runs ls`
```bash
❯ prefect flow-runs ls --help
                                                                                                                                                                                                              
 Usage: prefect flow-runs ls [OPTIONS]                                                                                                                                                                        
                                                                                                                                                                                                              
 View recent flow runs or flow runs for specific flows.                                                                                                                                                       
 Arguments:                                                                                                                                                                                                   
 flow_name: Name of the flow                                                                                                                                                                                  
 limit: Maximum number of flow runs to list                                                                                                                                                                   
 state: Name of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'PAUSED', 'SUSPENDED',      
 'AWAITINGRETRY', 'RETRYING', and 'LATE'.                                                                                                                                                                     
 state_type: Type of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'CRASHED', and         
 'PAUSED'.                                                                                                                                                                                                    
 Examples:                                                                                                                                                                                                    
 $ prefect flow-runs ls --state Running                                                                                                                                                                       
 $ prefect flow-runs ls --state Running --state late                                                                                                                                                          
 $ prefect flow-runs ls --state_type RUNNING                                                                                                                                                                  
 $ prefect flow-runs ls --state_type RUNNING --state_type FAILED                                                                                                                                              
                                                                                                                                                                                                              
╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --flow-name         TEXT     Name of the flow [default: None]                                                                                                                                              │
│ --limit             INTEGER  Maximum number of flow runs to list [default: 15]                                                                                                                             │
│ --state             TEXT     Name of the flow run's state [default: None]                                                                                                                                  │
│ --state-type        TEXT     Type of the flow run's state [default: None]                                                                                                                                  │
│ --help                       Show this message and exit.                                                                                                                                                   │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
---------------------
### `--state` option
#### Specifies helpful error when state name invalid
```bash
❯ prefect flow-runs ls --state nonexistent
Invalid state name. Options are Scheduled, Pending, Running, Completed, Failed, Cancelled, Crashed, Paused, Cancelling, Suspended, AwaitingRetry, Retrying, Late.
```
#### Allows for case-insensitive state name arg
```bash
❯ prefect flow-runs ls --state completed
                                                     Flow Runs                                                     
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃                                   ID ┃ Flow                  ┃ Name                  ┃ State     ┃ When         ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ c6c22580-9aa1-4459-ba99-df3375dc6d6f │ towering-infernflow   │ defiant-caracal       │ COMPLETED │ 1 week ago   │
│ 70f78024-f342-45bd-922d-afc81faae96b │ Second important name │ real-shark            │ COMPLETED │ 1 week ago   │
│ e34d5419-b9da-4681-8fdc-97fcf756bfef │ child_flow-0          │ practical-weasel      │ COMPLETED │ 3 months ago │
└──────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────┴──────────────┘
```
#### `AwaitingRetry` is a subtype of the `Scheduled` state
```bash
❯ prefect flow-runs ls --state awaitingretry
                                          Flow Runs                                          
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃                                   ID ┃ Flow    ┃ Name          ┃ State     ┃ When         ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ d68e9192-3ce5-49b3-9f38-af27b2011c1f │ my-flow │ beige-gorilla │ SCHEDULED │ 7 months ago │
└──────────────────────────────────────┴─────────┴───────────────┴───────────┴──────────────┘
```

#### `Late` is a subtype of the `Scheduled` state
```bash
❯ prefect flow-runs ls --state late
                                                    Flow Runs                                                     
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃                                   ID ┃ Flow                  ┃ Name               ┃ State     ┃ When           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ cf7f0253-52c8-406b-9603-e460e4a42771 │ Second important name │ piquant-ocelot     │ SCHEDULED │ 30 minutes ago │
│ 185f67c4-986a-4228-a838-5d8b1d639119 │ Second important name │ conscious-elephant │ SCHEDULED │ 1 hour ago     │
│ 1f0f97fe-60be-46b8-8324-d4830932f7d0 │ Second important name │ cherubic-porcupine │ SCHEDULED │ 2 hours ago    │
│ 2fa8dc6a-43c0-4b29-b8d8-a454dc5911bb │ Second important name │ groovy-skua        │ SCHEDULED │ 3 hours ago    │
│ 531d1036-9f31-44d8-87e7-89bfa86302a3 │ Second important name │ ambitious-koala    │ SCHEDULED │ 14 hours ago   │
└──────────────────────────────────────┴───────────────────────┴────────────────────┴───────────┴────────────────┘
```
#### Previously, we would show an empty table. I'm indifferent to whether we display this instead but it feels more explicit.
```bash
❯ prefect flow-runs ls --state suspended
No flow runs found.
```
-----------------------
### `--state-type` option
#### Specifies helpful error when state type invalid
```bash
❯ prefect flow-runs ls --state-type nonexistent
Invalid state type. Options are SCHEDULED, PENDING, RUNNING, COMPLETED, FAILED, CANCELLED, CRASHED, PAUSED, CANCELLING.
```
#### Allows for case-insensitive state type arg
```bash
❯ prefect flow-runs ls --state-type completed
                                                     Flow Runs                                                     
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃                                   ID ┃ Flow                  ┃ Name                  ┃ State     ┃ When         ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ c6c22580-9aa1-4459-ba99-df3375dc6d6f │ towering-infernflow   │ defiant-caracal       │ COMPLETED │ 1 week ago   │
│ 70f78024-f342-45bd-922d-afc81faae96b │ Second important name │ real-shark            │ COMPLETED │ 1 week ago   │
│ e34d5419-b9da-4681-8fdc-97fcf756bfef │ child_flow-0          │ practical-weasel      │ COMPLETED │ 3 months ago │
└──────────────────────────────────────┴───────────────────────┴───────────────────────┴───────────┴──────────────┘
```
#### Previously, we would show an empty table. I'm indifferent to whether we display this instead but it feels more explicit.
```bash
❯ prefect flow-runs ls --state-type paused
No flow runs found.
```

Problem statement:
Handle state parameter case in CLI `flow-run ls`
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar request and didn't find it.
- [X] I searched the Prefect documentation for this feature.

### Prefect Version

2.x

### Describe the current behavior

When running `prefect flow-run ls --state RUNNING` you do not get back running flows. If you run `prefect flow-run ls --state Running` you do. However, if you run `prefect flow-run ls --state-type RUNNING` you get running flows and if you run `prefect-flow-run ls --state-type Running` you get an error.

### Describe the proposed behavior

It would be great if the CLI could adjust these arguments on behalf of the user, converting `running` or `Running` to `RUNNING` for the `--state-type` argument and converting `RUNNING` to `Running` for the `--state` argument.

### Example Use

Ideally `prefect flow-run ls --state RUNNING` and `prefect flow-run ls --state Running` would return the same response.

### Additional context

This would just be more user friendly, especially since the same basic argument value is currently required to be in a different case for `--state` and `--state-type`.

Hints from issue discussion:
Oh, another frustration, the `State` column shows the values in all uppercase, even when running with `--state Running`, which makes it very easy to assume that uppercase is required when it actually doesn't work.
thanks for the issue @NodeJSmith! we've added this to the backlog
@NodeJSmith, this casing may be causing a lot of frustration while using the CLI. Thanks for raising this issue. 
@zzstoatzz, can you please assign this issue to me?

Commit message: add back last test

Patch to review:
diff --git a/src/prefect/cli/flow_run.py b/src/prefect/cli/flow_run.py
index a58868de01ae..808170e59230 100644
--- a/src/prefect/cli/flow_run.py
+++ b/src/prefect/cli/flow_run.py
@@ -19,7 +19,7 @@
 from prefect.cli.root import app
 from prefect.client.orchestration import get_client
 from prefect.client.schemas.filters import FlowFilter, FlowRunFilter, LogFilter
-from prefect.client.schemas.objects import StateType
+from prefect.client.schemas.objects import StateName, StateType
 from prefect.client.schemas.responses import SetStateStatus
 from prefect.client.schemas.sorting import FlowRunSort, LogSort
 from prefect.exceptions import ObjectNotFound
@@ -57,19 +57,57 @@ async def ls(
     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"
-    ),
+    state_type: List[str] = typer.Option(None, help="Type of the flow run's state"),
 ):
     """
-    View recent flow runs or flow runs for specific flows
+    View recent flow runs or flow runs for specific flows.
+
+    Arguments:
+
+        flow_name: Name of the flow
+
+        limit: Maximum number of flow runs to list. Defaults to 15.
+
+        state: Name of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'PAUSED', 'SUSPENDED', 'AWAITINGRETRY', 'RETRYING', and 'LATE'.
+
+        state_type: Type of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'CRASHED', and 'PAUSED'.
+
+    Examples:
+
+    $ prefect flow-runs ls --state Running
+
+    $ prefect flow-runs ls --state Running --state late
+
+    $ prefect flow-runs ls --state-type RUNNING
+
+    $ prefect flow-runs ls --state-type RUNNING --state-type FAILED
     """
 
+    # Handling `state` and `state_type` argument validity in the function instead of by specifying
+    # List[StateType] and List[StateName] in the type hints, allows users to provide
+    # case-insensitive arguments for `state` and `state_type`.
+
     state_filter = {}
     if state:
-        state_filter["name"] = {"any_": state}
+        upper_cased_states = [s.upper() for s in state]
+        if not all(s in StateName.__members__ for s in upper_cased_states):
+            exit_with_error(
+                f"Invalid state name. Options are {', '.join([name.value for name in StateName])}."
+            )
+        # `FlowRun.state_name` is expected to be capitalized, so retrieve the `StateName` enum value
+        state_filter["name"] = {
+            "any_": [StateName[s].value for s in upper_cased_states]
+        }
     if state_type:
-        state_filter["type"] = {"any_": state_type}
+        upper_cased_states = [s.upper() for s in state_type]
+        if not all(s in StateType.__members__ for s in upper_cased_states):
+            exit_with_error(
+                f"Invalid state type. Options are {', '.join(StateType.__members__)}."
+            )
+
+        state_filter["type"] = {
+            "any_": [StateType[s].value for s in upper_cased_states]
+        }
 
     async with get_client() as client:
         flow_runs = await client.read_flow_runs(
@@ -85,6 +123,9 @@ async def ls(
             )
         }
 
+        if not flow_runs:
+            exit_with_success("No flow runs found.")
+
     table = Table(title="Flow Runs")
     table.add_column("ID", justify="right", style="cyan", no_wrap=True)
     table.add_column("Flow", style="blue", no_wrap=True)
diff --git a/src/prefect/client/schemas/objects.py b/src/prefect/client/schemas/objects.py
index 91dde7f3ff9f..ecc8fb73fa95 100644
--- a/src/prefect/client/schemas/objects.py
+++ b/src/prefect/client/schemas/objects.py
@@ -90,6 +90,22 @@ class StateType(AutoEnum):
     CANCELLING = AutoEnum.auto()
 
 
+class StateName(AutoEnum):
+    SCHEDULED = "Scheduled"
+    PENDING = "Pending"
+    RUNNING = "Running"
+    COMPLETED = "Completed"
+    FAILED = "Failed"
+    CANCELLED = "Cancelled"
+    CRASHED = "Crashed"
+    PAUSED = "Paused"
+    CANCELLING = "Cancelling"
+    SUSPENDED = "Suspended"
+    AWAITINGRETRY = "AwaitingRetry"
+    RETRYING = "Retrying"
+    LATE = "Late"
+
+
 class WorkPoolStatus(AutoEnum):
     """Enumeration of work pool statuses."""
 
diff --git a/tests/cli/test_flow_run.py b/tests/cli/test_flow_run.py
index bc71c11677c2..4debe170fb23 100644
--- a/tests/cli/test_flow_run.py
+++ b/tests/cli/test_flow_run.py
@@ -149,20 +149,30 @@ def test_ls_flow_name_filter(
     )
 
 
+@pytest.mark.parametrize(
+    "state_type_1, state_type_2",
+    [
+        ("completed", "running"),
+        ("COMPLETED", "RUNNING"),
+        ("Completed", "Running"),
+    ],
+)
 def test_ls_state_type_filter(
     scheduled_flow_run,
     completed_flow_run,
     running_flow_run,
     late_flow_run,
+    state_type_1,
+    state_type_2,
 ):
     result = invoke_and_assert(
         command=[
             "flow-run",
             "ls",
             "--state-type",
-            "COMPLETED",
+            state_type_1,
             "--state-type",
-            "RUNNING",
+            state_type_2,
         ],
         expected_code=0,
     )
@@ -174,14 +184,33 @@ def test_ls_state_type_filter(
     )
 
 
+def test_ls_state_type_filter_invalid_raises():
+    invoke_and_assert(
+        command=["flow-run", "ls", "--state-type", "invalid"],
+        expected_code=1,
+        expected_output_contains=(
+            "Invalid state type. Options are SCHEDULED, PENDING, RUNNING, COMPLETED, FAILED, CANCELLED, CRASHED, PAUSED, CANCELLING."
+        ),
+    )
+
+
+@pytest.mark.parametrize(
+    "state_name",
+    [
+        "Late",
+        "LATE",
+        "late",
+    ],
+)
 def test_ls_state_name_filter(
     scheduled_flow_run,
     completed_flow_run,
     running_flow_run,
     late_flow_run,
+    state_name,
 ):
     result = invoke_and_assert(
-        command=["flow-run", "ls", "--state", "Late"],
+        command=["flow-run", "ls", "--state", state_name],
         expected_code=0,
     )
 
@@ -192,6 +221,16 @@ def test_ls_state_name_filter(
     )
 
 
+def test_ls_state_name_filter_invalid_raises():
+    invoke_and_assert(
+        command=["flow-run", "ls", "--state", "invalid"],
+        expected_code=1,
+        expected_output_contains=(
+            "Invalid state name. Options are Scheduled, Pending, Running, Completed, Failed, Cancelled, Crashed, Paused, Cancelling, Suspended, AwaitingRetry, Retrying, Late."
+        ),
+    )
+
+
 def test_ls_limit(
     scheduled_flow_run,
     completed_flow_run,


Code review comments:
diff_hunk: @@ -57,19 +57,57 @@ async def ls(
     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"
-    ),
+    state_type: List[str] = typer.Option(None, help="Type of the flow run's state"),
 ):
     """
-    View recent flow runs or flow runs for specific flows
+    View recent flow runs or flow runs for specific flows.
+
+    Arguments:
+
+        flow_name: Name of the flow
+
+        limit: Maximum number of flow runs to list. Defaults to 15.
+
+        state: Name of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'PAUSED', 'SUSPENDED', 'AWAITINGRETRY', 'RETRYING', and 'LATE'.
+
+        state_type: Type of the flow run's state. Can be provided multiple times. Options are 'SCHEDULED', 'PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CRASHED', 'CANCELLING', 'CANCELLED', 'CRASHED', and 'PAUSED'.
+
+    Examples:
+
+    $ prefect flow-runs ls --state Running
+
+    $ prefect flow-runs ls --state Running --state late
+
+    $ prefect flow-runs ls --state-type RUNNING
+
+    $ prefect flow-runs ls --state-type RUNNING --state-type FAILED
     """
 
+    # Handling `state` and `state_type` argument validity in the function instead of by specifying
+    # List[StateType] and List[StateName] in the type hints, allows users to provide
+    # case-insensitive arguments for `state` and `state_type`.
+
     state_filter = {}
     if state:
-        state_filter["name"] = {"any_": state}
+        upper_cased_states = [s.upper() for s in state]
+        if not all(s in StateName.__members__ for s in upper_cased_states):
+            exit_with_error(
+                f"Invalid state name. Options are {', '.join([name.value for name in StateName])}."
+            )
path: src/prefect/cli/flow_run.py
text: @user1:
Since users can freely create new state _names_, do we want to be this strict on the name search?  (totally agree on being strict with the _type_ search)

@author:
Yeah good catch! Since we don't advertise that users can create new state names, it has slipped my mind. 

I've applied changes in [5ba6563](https://github.com/PrefectHQ/prefect/pull/13152/commits/5ba6563473175f9288506542a72a13b854743627) and updated the test in [3315e5b](https://github.com/PrefectHQ/prefect/pull/13152/commits/3315e5bc464f032ec09dd0d3e3e591cb981d470b)

diff_hunk: @@ -90,6 +90,22 @@ class StateType(AutoEnum):
     CANCELLING = AutoEnum.auto()
 
 
+class StateName(AutoEnum):
+    SCHEDULED = "Scheduled"
path: src/prefect/client/schemas/objects.py
text: @user1:
Same comment here about user-produced state names, I think this enum conflicts with that idea

@author:
Makes sense! I've removed this in [ec46ace](https://github.com/PrefectHQ/prefect/pull/13152/commits/ec46ace810e1ce1357b5ff05d5b4d94f19f39e87)