Repository: iterative/dvc

Issue:
queue kill: add a message when it's successfully run
If you successfully run the `dvc queue kill` command, you don't get any response. You don't see any message in the terminal and the DVC VSCode extension returns a generic message:

<img width="450" alt="image" src="https://user-images.githubusercontent.com/43496356/211827510-93365426-44aa-48dc-8b45-531a8947a644.png">

![image](https://user-images.githubusercontent.com/43496356/211827561-c6ed46e7-9153-411a-ac3c-8ca4c0407684.png)

Could we make it more clear that the command was successful by returning a message? `Killed experiment EXP_NAME` for example? 


Discussion:
Classic unix behaviour. 😄 
I think it is upto VSCode extension for the output, the success code is enough. 
> I think it is upto VSCode extension for the output, the success code is enough.

We want to avoid diverging from command outputs. [See #3084 thread](https://github.com/iterative/vscode-dvc/pull/3084#discussion_r1065875992). 
We put out simple outputs like this (and sometimes way more) on success for other commands. No reason not to do it here. The clig.dev guideline states:

> **Display output on success, but keep it brief.** Traditionally, when nothing is wrong, UNIX commands display no output to the user. This makes sense when they’re being used in scripts, but can make commands appear to be hanging or broken when used by humans. For example, cp will not print anything, even if it takes a long time.
>
> It’s rare that printing nothing at all is the best default behavior, but it’s usually best to err on the side of less.
>
> For instances where you do want no output (for example, when used in shell scripts), to avoid clumsy redirection of stderr to /dev/null, you can provide a -q option to suppress all non-essential output.

I think we had bigger problems when this was first implemented and didn't polish the UI very much. I see no reason not to show the experiments that were successfully killed in the output. cc @karajan1001 

Resolving patch:
diff --git a/dvc/repo/experiments/exceptions.py b/dvc/repo/experiments/exceptions.py
--- a/dvc/repo/experiments/exceptions.py
+++ b/dvc/repo/experiments/exceptions.py
@@ -128,5 +128,9 @@ class UnresolvedQueueExpNamesError(UnresolvedExpNamesError):
     NAME = "queued experiment name"
 
 
+class UnresolvedRunningExpNamesError(UnresolvedExpNamesError):
+    NAME = "running experiment name"
+
+
 class ExpQueueEmptyError(DvcException):
     pass
diff --git a/dvc/repo/experiments/queue/celery.py b/dvc/repo/experiments/queue/celery.py
--- a/dvc/repo/experiments/queue/celery.py
+++ b/dvc/repo/experiments/queue/celery.py
@@ -21,7 +21,10 @@
 
 from dvc.daemon import daemonize
 from dvc.exceptions import DvcException
-from dvc.repo.experiments.exceptions import UnresolvedQueueExpNamesError
+from dvc.repo.experiments.exceptions import (
+    UnresolvedQueueExpNamesError,
+    UnresolvedRunningExpNamesError,
+)
 from dvc.repo.experiments.executor.base import ExecutorInfo, ExecutorResult
 from dvc.repo.experiments.refs import CELERY_STASH
 from dvc.repo.experiments.utils import EXEC_TMP_DIR, get_exp_rwlock
@@ -317,7 +320,7 @@ def _try_to_kill_tasks(
                     self.proc.kill(queue_entry.stash_rev)
                 else:
                     self.proc.interrupt(queue_entry.stash_rev)
-                logger.debug(f"Task {rev} had been killed.")
+                ui.write(f"{rev} has been killed.")
             except ProcessLookupError:
                 fail_to_kill_entries[queue_entry] = rev
         return fail_to_kill_entries
@@ -373,12 +376,12 @@ def kill(self, revs: Collection[str], force: bool = False) -> None:
             else:
                 to_kill[queue_entry] = rev
 
-        if missing_revs:
-            raise UnresolvedQueueExpNamesError(missing_revs)
-
         if to_kill:
             self._kill_entries(to_kill, force)
 
+        if missing_revs:
+            raise UnresolvedRunningExpNamesError(missing_revs)
+
     def shutdown(self, kill: bool = False):
         self.celery.control.shutdown()
         if kill:
@@ -406,8 +409,8 @@ def logs(
             {rev}, self.iter_active(), self.iter_done()
         ).get(rev)
         if queue_entry is None:
-            if rev in self.match_queue_entry_by_name(
-                {rev}, self.iter_queued()
+            if self.match_queue_entry_by_name({rev}, self.iter_queued()).get(
+                rev
             ):
                 raise DvcException(
                     f"Experiment '{rev}' is in queue but has not been started"


Test patch:
diff --git a/tests/unit/repo/experiments/queue/test_celery.py b/tests/unit/repo/experiments/queue/test_celery.py
--- a/tests/unit/repo/experiments/queue/test_celery.py
+++ b/tests/unit/repo/experiments/queue/test_celery.py
@@ -90,14 +90,6 @@ def test_celery_queue_kill(test_queue, mocker, force):
         "iter_active",
         return_value={mock_entry_foo, mock_entry_bar, mock_entry_foobar},
     )
-    mocker.patch.object(
-        test_queue,
-        "match_queue_entry_by_name",
-        return_value={"bar": None},
-    )
-    with pytest.raises(UnresolvedExpNamesError):
-        test_queue.kill("bar")
-
     mocker.patch.object(
         test_queue,
         "match_queue_entry_by_name",
@@ -151,6 +143,31 @@ def kill_function(rev):
     mark_mocker.assert_called_once_with("bar", None)
 
 
+@pytest.mark.parametrize("force", [True, False])
+def test_celery_queue_kill_invalid(test_queue, mocker, force):
+
+    mock_entry_foo = mocker.Mock(stash_rev="foo")
+    mock_entry_bar = mocker.Mock(stash_rev="bar")
+
+    mocker.patch.object(
+        test_queue,
+        "match_queue_entry_by_name",
+        return_value={
+            "bar": mock_entry_bar,
+            "foo": mock_entry_foo,
+            "foobar": None,
+        },
+    )
+
+    kill_mock = mocker.patch.object(test_queue, "_kill_entries")
+
+    with pytest.raises(UnresolvedExpNamesError):
+        test_queue.kill(["bar", "foo", "foobar"], force=force)
+    assert kill_mock.called_once_with(
+        {mock_entry_foo: "foo", mock_entry_bar: "bar"}, force
+    )
+
+
 @pytest.mark.parametrize("status", ["FAILURE", "SUCCESS"])
 def test_queue_iter_done_task(test_queue, mocker, status):