From 923e44199f0a478fa738b2110509335ae8a4b468 Mon Sep 17 00:00:00 2001 From: rcmerci Date: Sat, 31 Jan 2026 20:33:58 +0800 Subject: [PATCH] 025-logseq-cli-builtin-status-priority-queries.md --- ...seq-cli-builtin-status-priority-queries.md | 94 +++++++++++++++++++ docs/cli/logseq-cli.md | 3 + src/main/logseq/cli/command/query.cljs | 29 ++++-- src/test/logseq/cli/command/query_test.cljs | 43 ++++++++- src/test/logseq/cli/integration_test.cljs | 53 +++++++++++ 5 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 docs/agent-guide/025-logseq-cli-builtin-status-priority-queries.md diff --git a/docs/agent-guide/025-logseq-cli-builtin-status-priority-queries.md b/docs/agent-guide/025-logseq-cli-builtin-status-priority-queries.md new file mode 100644 index 0000000000..b185015d8b --- /dev/null +++ b/docs/agent-guide/025-logseq-cli-builtin-status-priority-queries.md @@ -0,0 +1,94 @@ +# Logseq CLI Built-in Status/Priority Queries Plan + +Goal: Add built-in `query` names `list-status` and `list-priority` that return the available Status/Priority options from a graph. +Architecture: Keep the existing logseq-cli → db-worker-node transport and thread-api usage; implement `list-status`/`list-priority` via `:thread-api/q` rather than adding a dedicated thread-api. +Tech Stack: ClojureScript, logseq-cli, db-worker-node, Datascript, logseq.db.frontend.property. +Related: `src/main/logseq/cli/command/query.cljs`, `src/main/frontend/worker/db_core.cljs`, `deps/db/src/logseq/db/frontend/property.cljs`. + +## Problem Statement + +Logseq CLI’s `query` built-ins are limited to Datascript queries. Users cannot easily discover valid Status or Priority options for a graph (e.g., TODO/DOING/DONE, Urgent/High/Low) without knowing the property internals or running custom queries. We need simple built-ins that list the closed values configured for Status and Priority. + +## Non-Goals + +- Changing property schemas or defaults. +- Replacing the existing Datascript query flow. +- Adding new UI commands outside `query` or changing CLI output formats. + +## Current Behavior (Key Points) + +- Built-in queries are defined in `built-in-query-specs` in `src/main/logseq/cli/command/query.cljs` and executed via `:thread-api/q`. +- Status and Priority options are stored as closed values on `:logseq.property/status` and `:logseq.property/priority` (see `logseq.db.frontend.property/get-closed-property-values`). +- There is no CLI helper to list those closed values. + +## Proposed Changes + +1) **Use `:thread-api/q` for closed values** + - Implement `list-status`/`list-priority` by issuing a Datascript query via `:thread-api/q`. + - Return a vector of maps with `:db/ident` and `:db/id`. + - Use `:find` with an ellipsis form to return a vector, e.g. `:find [(pull ?e [:db/ident :db/id]) ...]` (instead of `:find (pull ?e [:db/ident :db/id])`). + +2) **Add built-in query names `list-status` and `list-priority`** + - Add entries to `built-in-query-specs` that specify a non-Datascript execution path (e.g., `:method`/`:handler` metadata). + - Wire `build-action` to create a dedicated action type when these names are used. + - Update `execute-query` to call the new thread API and return results as `{:result ["TODO" ...]}` so existing output formatting works unchanged. + +3) **Expose built-ins in `query list`** + - Ensure `query list` includes the new entries (with `doc` and `inputs: []`). + - Keep existing “custom overrides built-in” semantics. + +## Implementation Plan + +1) **db-worker-node API** + - No new thread-api should be added; use `:thread-api/q` only. + +2) **CLI built-in wiring** + - Extend `built-in-query-specs` in `src/main/logseq/cli/command/query.cljs`: + - `list-status` → `:property-ident :logseq.property/status` + - `list-priority` → `:property-ident :logseq.property/priority` + - Include `:doc` and `:inputs []`. + - Update `normalize-query-entry` / `find-query` handling to preserve the extra metadata. + - Update `build-action`: + - When the built-in includes a `:property-ident` (or `:method`), create an action like + `{:type :query-closed-values :repo ... :property-ident ...}`. + - Update `execute-query`: + - Branch on the new action type to call `transport/invoke` with `:thread-api/q` and return `{:result values}` (vector of maps with `:db/ident` and `:db/id`). + +3) **Output and docs** + - No change to formatters; `format-query-results` already prints vectors. + - Update CLI docs if needed (e.g., `docs/cli/logseq-cli.md`) to mention the two built-ins. + +## Testing Plan + +- **Unit tests** (`src/test/logseq/cli/command/query_test.cljs`): + - `list-queries` includes `list-status` and `list-priority` with empty inputs. + - `build-action` for `--name list-status` returns `:query-closed-values` action with property ident. + - Custom query overrides built-in name still works. + +- **db-worker-node tests**: no new thread-api, so no additional db-worker-node test needed. + +- **Integration** (`src/test/logseq/cli/integration_test.cljs`): + - Start a graph, run `logseq query --name list-status` / `list-priority`, assert status `ok` and a non-empty vector. + - If stable defaults are known, assert a known value is present; otherwise, seed closed values in the test graph. + +## Edge Cases + +- Property has no closed values → return an empty vector (not an error). +- Closed values may be stored in either `:block/title` or `:logseq.property/value`; if `:db/ident` is absent, the map should still include the key with a nil value. +- Ensure ordering follows `:block/order` from `get-closed-property-values`. + +## Files to Touch + +- `src/main/frontend/worker/db_core.cljs` (no new thread-api) +- `src/main/logseq/cli/command/query.cljs` (built-in specs, build-action branching, execute-query) +- `src/test/logseq/cli/command/query_test.cljs` (unit coverage) +- `src/test/frontend/worker/db_worker_node_test.cljs` or a db-core test (not required for new thread-api) +- `src/test/logseq/cli/integration_test.cljs` (CLI end-to-end) +- `docs/cli/logseq-cli.md` (optional docs update) + +## Open Questions + +- Use `:thread-api/q` to return structured values: `{:db/ident .., :db/id ..}`. +- Expose `list-status`/`list-priority` via `query --name` only (no dedicated subcommands). + +--- diff --git a/docs/cli/logseq-cli.md b/docs/cli/logseq-cli.md index 311d3cea1f..379aa9a3a7 100644 --- a/docs/cli/logseq-cli.md +++ b/docs/cli/logseq-cli.md @@ -78,6 +78,8 @@ Inspect and edit commands: - `remove --id |--uuid |--page ` - remove blocks (by db/id or UUID) or pages - `search [--type page|block|tag|property|all] [--tag ] [--case-sensitive] [--sort updated-at|created-at] [--order asc|desc]` - search across pages, blocks, tags, and properties (query is positional) - `query --query [--inputs ]` - run a Datascript query against the graph +- `query --name [--inputs ]` - run a named query (built-in or from `cli.edn`) +- `query list` - list available named queries - `show --page [--level ]` - show page tree - `show --uuid [--level ]` - show block tree - `show --id [--level ]` - show block tree by db/id @@ -113,6 +115,7 @@ Output formats: - For `graph export`, `--output` refers to the destination file path. Output formatting is controlled via `:output-format` in config or `LOGSEQ_CLI_OUTPUT`. - Human output is plain text. List/search commands render tables with a final `Count: N` line. For list and search subcommands, the ID column uses `:db/id` (not UUID). If `:db/ident` exists, an `IDENT` column is included. Search table columns are `ID` and `TITLE`. Block titles can include multiple lines; multi-line rows align additional lines under the `TITLE` column. Times such as list `UPDATED-AT`/`CREATED-AT` and `graph info` `Created at` are shown in human-friendly relative form. Errors include error codes and may include a `Hint:` line. Use `--output json|edn` for structured output. - `query` human output returns a plain string (the query result rendered via `pr-str`), which is convenient for pipelines like `logseq query ... | xargs logseq show --id`. +- Built-in named queries currently include `block-search`, `task-search`, `recent-updated`, `list-status`, and `list-priority`. Use `query list` to see the full set for your config. - Show and search outputs resolve block reference UUIDs inside text, replacing `[[]]` with the referenced block content. Nested references are resolved recursively up to 10 levels to avoid excessive expansion. For example: `[[]]` → `[[some text [[]]]]` and then `` is also replaced. - `show` human output prints the `:db/id` as the first column followed by a tree: diff --git a/src/main/logseq/cli/command/query.cljs b/src/main/logseq/cli/command/query.cljs index ebf61d89eb..87b86dcf9b 100644 --- a/src/main/logseq/cli/command/query.cljs +++ b/src/main/logseq/cli/command/query.cljs @@ -69,7 +69,23 @@ [(missing? $ ?e :logseq.property/built-in?)] [(* ?recent-days 86400000) ?recent-days-ms] [(- ?now-ms ?recent-days-ms) ?days-ago] - [(>= ?updated-at ?days-ago)]]}}) + [(>= ?updated-at ?days-ago)]]} + + "list-status" + {:doc "List closed values for the Status property." + :inputs [] + :query '[:find [(pull ?value [:db/id :db/ident :block/order]) ...] + :where + [?property :db/ident :logseq.property/status] + [?value :block/closed-value-property ?property]]} + + "list-priority" + {:doc "List closed values for the Priority property." + :inputs [] + :query '[:find [(pull ?value [:db/id :db/ident :block/order]) ...] + :where + [?property :db/ident :logseq.property/priority] + [?value :block/closed-value-property ?property]]}}) (defn- parse-edn [label value] @@ -264,11 +280,12 @@ (defn execute-query [action config] - (-> (p/let [cfg (cli-server/ensure-server! config (:repo action)) - args (into [(:query action)] (:inputs action)) - results (transport/invoke cfg :thread-api/q false [(:repo action) args])] - {:status :ok - :data {:result results}}))) + (case (:type action) + (-> (p/let [cfg (cli-server/ensure-server! config (:repo action)) + args (into [(:query action)] (:inputs action)) + results (transport/invoke cfg :thread-api/q false [(:repo action) args])] + {:status :ok + :data {:result results}})))) (defn execute-query-list [_action config] diff --git a/src/test/logseq/cli/command/query_test.cljs b/src/test/logseq/cli/command/query_test.cljs index a6becea914..1c28aa6944 100644 --- a/src/test/logseq/cli/command/query_test.cljs +++ b/src/test/logseq/cli/command/query_test.cljs @@ -79,7 +79,7 @@ "logseq_db_demo" config)] (is (true? (:ok? result))) - (is (= ["doing" nil nil] (get-in result [:action :inputs]))))) + (is (= [:logseq.property/status.doing nil nil] (get-in result [:action :inputs]))))) (testing "missing required inputs returns invalid-options" (let [config {:custom-queries {"task-search" @@ -109,7 +109,7 @@ "logseq_db_demo" config)] (is (true? (:ok? result))) - (is (= ["doing" "fallback-title" 7] (get-in result [:action :inputs]))))) + (is (= [:logseq.property/status.doing "fallback-title" 7] (get-in result [:action :inputs]))))) (testing "built-in task-search uses defaults for optional inputs" (let [result (query-command/build-action {:name "task-search" @@ -118,18 +118,53 @@ {})] (is (true? (:ok? result))) (let [inputs (get-in result [:action :inputs])] - (is (= ["doing" "" 0] (subvec inputs 0 3))) + (is (= [:logseq.property/status.doing "" 0] (subvec inputs 0 3))) (is (number? (nth inputs 3))))))) +(deftest test-build-action-closed-value-queries + (testing "list-status builds standard query action" + (let [result (query-command/build-action {:name "list-status"} + "logseq_db_demo" + {})] + (is (true? (:ok? result))) + (is (= :query (get-in result [:action :type]))) + (is (= "logseq_db_demo" (get-in result [:action :repo]))) + (is (= '[:find [(pull ?value [:db/id :db/ident :block/order]) ...] + :where + [?property :db/ident :logseq.property/status] + [?value :block/closed-value-property ?property]] + (get-in result [:action :query]))))) + + (testing "list-priority builds standard query action" + (let [result (query-command/build-action {:name "list-priority"} + "logseq_db_demo" + {})] + (is (true? (:ok? result))) + (is (= :query (get-in result [:action :type]))) + (is (= :logseq.property/priority (get-in result [:action :query 3 2]))) + (is (= '[:find [(pull ?value [:db/id :db/ident :block/order]) ...] + :where + [?property :db/ident :logseq.property/priority] + [?value :block/closed-value-property ?property]] + (get-in result [:action :query])))))) + (deftest test-query-list-merges-built-in-and-custom (testing "built-in and custom queries are both listed" (let [queries (query-command/list-queries {:custom-queries {"custom-q" {:query '[:find ?e]}}}) names (set (map :name queries))] (is (contains? names "block-search")) (is (contains? names "task-search")) + (is (contains? names "list-status")) + (is (contains? names "list-priority")) (is (contains? names "custom-q")))) (testing "custom query overrides built-in name" (let [queries (query-command/list-queries {:custom-queries {"block-search" {:query '[:find ?e]}}}) block-search (first (filter #(= "block-search" (:name %)) queries))] - (is (= :custom (:source block-search)))))) + (is (= :custom (:source block-search))))) + + (testing "list-status and list-priority have empty inputs" + (let [queries (query-command/list-queries {}) + by-name (into {} (map (fn [entry] [(:name entry) entry]) queries))] + (is (= [] (get-in by-name ["list-status" :inputs]))) + (is (= [] (get-in by-name ["list-priority" :inputs])))))) diff --git a/src/test/logseq/cli/integration_test.cljs b/src/test/logseq/cli/integration_test.cljs index 2993f6d998..23131a9fad 100644 --- a/src/test/logseq/cli/integration_test.cljs +++ b/src/test/logseq/cli/integration_test.cljs @@ -621,6 +621,59 @@ (is false (str "unexpected error: " e)) (done))))))) +(deftest test-cli-query-list-status-priority + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-status-query")] + (-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") + _ (fs/writeFileSync cfg-path "{:output-format :json}") + create-result (run-cli ["graph" "create" "--repo" "status-query-graph"] data-dir cfg-path) + create-payload (parse-json-output create-result) + _ (p/delay 100) + list-result (run-cli ["query" "list"] data-dir cfg-path) + list-payload (parse-json-output list-result) + names (set (map :name (get-in list-payload [:data :queries]))) + status-result (run-cli ["--repo" "status-query-graph" + "query" + "--name" "list-status"] + data-dir cfg-path) + status-payload (parse-json-output status-result) + status-values (get-in status-payload [:data :result]) + priority-result (run-cli ["--repo" "status-query-graph" + "query" + "--name" "list-priority"] + data-dir cfg-path) + priority-payload (parse-json-output priority-result) + priority-values (get-in priority-payload [:data :result]) + stop-result (run-cli ["server" "stop" "--repo" "status-query-graph"] data-dir cfg-path) + stop-payload (parse-json-output stop-result)] + (is (= "ok" (:status create-payload))) + (is (= "ok" (:status list-payload))) + (is (contains? names "list-status")) + (is (contains? names "list-priority")) + (is (= 0 (:exit-code status-result))) + (is (= "ok" (:status status-payload))) + (is (vector? status-values)) + (when (seq status-values) + (let [row (first status-values) + value (if (vector? row) (first row) row)] + (is (map? value)) + (is (contains? value :ident)) + (is (contains? value :id)))) + (is (= 0 (:exit-code priority-result))) + (is (= "ok" (:status priority-payload))) + (is (vector? priority-values)) + (when (seq priority-values) + (let [row (first priority-values) + value (if (vector? row) (first row) row)] + (is (map? value)) + (is (contains? value :ident)) + (is (contains? value :id)))) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + (deftest test-cli-query-recent-updated (async done (let [data-dir (node-helper/create-tmp-dir "db-worker-recent-updated")]