From eec5b4a060f3a391cf8fb5fbb800af650c25b34f Mon Sep 17 00:00:00 2001 From: Gabriel Horner Date: Fri, 13 Mar 2026 16:17:20 -0400 Subject: [PATCH] fix: multiple issues with --fields option for list* commands - 4 visible fields couldn't be used with --fields - Only print selected fields/columns. Previous behavior didn't make sense as unspecified fields were still listed and took up horizontal space - Allow --fields to work when --expand isn't used. Previous behavior wasn't explained to user and needlessly limiting --- .../004-logseq-cli-verb-subcommands.md | 6 +- src/main/logseq/cli/command/list.cljs | 26 +++--- src/main/logseq/cli/format.cljs | 80 ++++++++----------- src/test/logseq/cli/format_test.cljs | 10 ++- src/test/logseq/cli/integration_test.cljs | 43 ++++++++-- 5 files changed, 94 insertions(+), 71 deletions(-) diff --git a/docs/agent-guide/004-logseq-cli-verb-subcommands.md b/docs/agent-guide/004-logseq-cli-verb-subcommands.md index 960268f193..acb001ed60 100644 --- a/docs/agent-guide/004-logseq-cli-verb-subcommands.md +++ b/docs/agent-guide/004-logseq-cli-verb-subcommands.md @@ -79,7 +79,7 @@ List page options: | --include-hidden | Include hidden pages | Requires a flag to bypass entity-util/hidden? filtering. | | --updated-after ISO8601 | Filter by updated-at | Compare to :block/updated-at. | | --created-after ISO8601 | Filter by created-at | Compare to :block/created-at. | -| --fields FIELD,FIELD | Select output fields | Applies when --expand is true. | +| --fields FIELD,FIELD | Select output fields | | List tag options: @@ -88,7 +88,7 @@ List tag options: | --include-built-in | Include built-in classes | Built-in tags are currently included by default, clarify behavior. | | --with-properties | Include class properties | Uses :logseq.property.class/properties when expanded. | | --with-extends | Include class extends | Uses :logseq.property.class/extends when expanded. | -| --fields FIELD,FIELD | Select output fields | Applies when --expand is true. | +| --fields FIELD,FIELD | Select output fields | | List property options: @@ -97,7 +97,7 @@ List property options: | --include-built-in | Include built-in properties | Built-in properties are currently included by default, clarify behavior. | | --with-classes | Include property classes | Uses :logseq.property/classes when expanded. | | --with-type | Include property type | Uses :logseq.property/type when expanded. | -| --fields FIELD,FIELD | Select output fields | Applies when --expand is true. | +| --fields FIELD,FIELD | Select output fields | | List block is removed to avoid overlap with search. diff --git a/src/main/logseq/cli/command/list.cljs b/src/main/logseq/cli/command/list.cljs index e21b28e5f6..30ea36bae8 100644 --- a/src/main/logseq/cli/command/list.cljs +++ b/src/main/logseq/cli/command/list.cljs @@ -94,23 +94,31 @@ :options options}})) (def ^:private list-page-field-map - {"title" :block/title + {"id" :db/id + "ident" :db/ident + "title" :block/title "uuid" :block/uuid "created-at" :block/created-at "updated-at" :block/updated-at}) (def ^:private list-tag-field-map - {"name" :block/title + {"id" :db/id + "ident" :db/ident "title" :block/title "uuid" :block/uuid + "created-at" :block/created-at + "updated-at" :block/updated-at "properties" :logseq.property.class/properties "extends" :logseq.property.class/extends "description" :logseq.property/description}) (def ^:private list-property-field-map - {"name" :block/title + {"id" :db/id + "ident" :db/ident "title" :block/title "uuid" :block/uuid + "created-at" :block/created-at + "updated-at" :block/updated-at "classes" :logseq.property/classes "type" :logseq.property/type "description" :logseq.property/description}) @@ -178,9 +186,7 @@ fields (parse-field-list (:fields options)) sorted (apply-sort items (:sort options) order list-page-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) - final (if (:expand options) - (apply-fields limited fields list-page-field-map) - limited)] + final (apply-fields limited fields list-page-field-map)] {:status :ok :data {:items final}}))) @@ -195,9 +201,7 @@ prepared (mapv #(prepare-tag-item % options) items) sorted (apply-sort prepared (:sort options) order list-tag-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) - final (if (:expand options) - (apply-fields limited fields list-tag-field-map) - limited)] + final (apply-fields limited fields list-tag-field-map)] {:status :ok :data {:items final}}))) @@ -212,8 +216,6 @@ prepared (mapv #(prepare-property-item % options) items) sorted (apply-sort prepared (:sort options) order list-property-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) - final (if (:expand options) - (apply-fields limited fields list-property-field-map) - limited)] + final (apply-fields limited fields list-property-field-map)] {:status :ok :data {:items final}}))) diff --git a/src/main/logseq/cli/format.cljs b/src/main/logseq/cli/format.cljs index 75b935c536..778b66a7b7 100644 --- a/src/main/logseq/cli/format.cljs +++ b/src/main/logseq/cli/format.cljs @@ -122,11 +122,6 @@ candidates* (str candidates*) hint (str "\nHint: " hint))))) -(defn- maybe-ident-header - [items] - (when (some :db/ident items) - ["IDENT"])) - (defn- parse-ts [value] (cond @@ -154,37 +149,36 @@ :else (str years "y ago"))) "-")) -(defn- format-list-row - [item include-ident? now-ms] - (let [base [(or (:db/id item) (:id item)) - (or (:title item) (:block/title item) (:name item))] - with-ident (cond-> base - include-ident? (conj (:db/ident item))) - updated (human-ago (or (:updated-at item) (:block/updated-at item)) now-ms) - created (human-ago (or (:created-at item) (:block/created-at item)) now-ms)] - (conj with-ident updated created))) +(defn- items-have-key? + [items & ks] + (some (fn [item] (some #(contains? item %) ks)) items)) + +(def ^:private list-columns + [["ID" (fn [item _] (or (:db/id item) (:id item))) [:db/id :id]] + ["TITLE" (fn [item _] (or (:title item) (:block/title item) (:name item))) [:title :block/title :name]] + ["IDENT" (fn [item _] (:db/ident item)) [:db/ident]] + ["UPDATED-AT" (fn [item now-ms] (human-ago (or (:updated-at item) (:block/updated-at item)) now-ms)) [:updated-at :block/updated-at]] + ["CREATED-AT" (fn [item now-ms] (human-ago (or (:created-at item) (:block/created-at item)) now-ms)) [:created-at :block/created-at]]]) + +(defn- format-list-dynamic + [items now-ms columns] + (let [items (or items []) + active (filterv (fn [[_ _ ks always?]] + (or always? (apply items-have-key? items ks))) + columns) + headers (mapv first active) + rows (mapv (fn [item] + (mapv (fn [[_ extractor _]] (extractor item now-ms)) active)) + items)] + (format-counted-table headers rows))) (defn- format-list-page [items now-ms] - (let [items (or items []) - include-ident? (boolean (some :db/ident items)) - headers (into ["ID" "TITLE"] - (concat (or (maybe-ident-header items) []) - ["UPDATED-AT" "CREATED-AT"]))] - (format-counted-table - headers - (mapv #(format-list-row % include-ident? now-ms) items)))) + (format-list-dynamic items now-ms list-columns)) (defn- format-list-tag [items now-ms] - (let [items (or items []) - include-ident? (boolean (some :db/ident items)) - headers (into ["ID" "TITLE"] - (concat (or (maybe-ident-header items) []) - ["UPDATED-AT" "CREATED-AT"]))] - (format-counted-table - headers - (mapv #(format-list-row % include-ident? now-ms) items)))) + (format-list-dynamic items now-ms list-columns)) (defn- normalize-property-type [value] @@ -193,27 +187,17 @@ (nil? value) "-" :else (str value))) -(defn- format-list-property-row - [item include-ident? now-ms] - (let [base [(or (:db/id item) (:id item)) - (or (:title item) (:block/title item) (:name item)) - (normalize-property-type (:logseq.property/type item))] - with-ident (cond-> base - include-ident? (conj (:db/ident item))) - updated (human-ago (or (:updated-at item) (:block/updated-at item)) now-ms) - created (human-ago (or (:created-at item) (:block/created-at item)) now-ms)] - (conj with-ident updated created))) +(def ^:private list-property-columns + [["ID" (fn [item _] (or (:db/id item) (:id item))) [:db/id :id]] + ["TITLE" (fn [item _] (or (:title item) (:block/title item) (:name item))) [:title :block/title :name]] + ["TYPE" (fn [item _] (normalize-property-type (:logseq.property/type item))) [:logseq.property/type]] + ["IDENT" (fn [item _] (:db/ident item)) [:db/ident]] + ["UPDATED-AT" (fn [item now-ms] (human-ago (or (:updated-at item) (:block/updated-at item)) now-ms)) [:updated-at :block/updated-at]] + ["CREATED-AT" (fn [item now-ms] (human-ago (or (:created-at item) (:block/created-at item)) now-ms)) [:created-at :block/created-at]]]) (defn- format-list-property [items now-ms] - (let [items (or items []) - include-ident? (boolean (some :db/ident items)) - headers (into ["ID" "TITLE" "TYPE"] - (concat (or (maybe-ident-header items) []) - ["UPDATED-AT" "CREATED-AT"]))] - (format-counted-table - headers - (mapv #(format-list-property-row % include-ident? now-ms) items)))) + (format-list-dynamic items now-ms list-property-columns)) (defn- format-graph-list [graphs current-graph] diff --git a/src/test/logseq/cli/format_test.cljs b/src/test/logseq/cli/format_test.cljs index 2985f9d4ec..639e34e5eb 100644 --- a/src/test/logseq/cli/format_test.cljs +++ b/src/test/logseq/cli/format_test.cljs @@ -133,15 +133,21 @@ (testing "list property renders missing type as -" (let [result (format/format-result {:status :ok :command :list-property - :data {:items [{:block/title "Untyped" + :data {:items [{:block/title "Prop" + :db/id 99 + :logseq.property/type :node + :block/created-at 40000 + :block/updated-at 90000} + {:block/title "Untyped" :db/id 100 :block/created-at 40000 :block/updated-at 90000}]}} {:output-format nil :now-ms 100000})] (is (= (str "ID TITLE TYPE UPDATED-AT CREATED-AT\n" + "99 Prop node 10s ago 1m ago\n" "100 Untyped - 10s ago 1m ago\n" - "Count: 1") + "Count: 2") result))))) (deftest test-human-output-add-upsert-remove diff --git a/src/test/logseq/cli/integration_test.cljs b/src/test/logseq/cli/integration_test.cljs index 273859b35e..685ad86fff 100644 --- a/src/test/logseq/cli/integration_test.cljs +++ b/src/test/logseq/cli/integration_test.cljs @@ -200,7 +200,7 @@ :updated-at 1735686000000} overrides))) -(deftest test-cli-login-integration +(deftest ^:long test-cli-login-integration (async done (let [data-dir (node-helper/create-tmp-dir "cli-login-data") cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") @@ -237,7 +237,7 @@ (p/finally (fn [] (done)))))))) -(deftest test-cli-logout-integration +(deftest ^:long test-cli-logout-integration (async done (let [data-dir (node-helper/create-tmp-dir "cli-logout-data") cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") @@ -270,7 +270,7 @@ (p/finally (fn [] (done)))))))) -(deftest test-cli-sync-remote-graphs-refreshes-auth-file-and-injects-runtime-token +(deftest ^:long test-cli-sync-remote-graphs-refreshes-auth-file-and-injects-runtime-token (async done (let [data-dir (node-helper/create-tmp-dir "cli-sync-auth") cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") @@ -316,7 +316,7 @@ (p/finally (fn [] (done)))))))) -(deftest test-cli-sync-download-and-start-readiness-with-mocked-sync +(deftest ^:long test-cli-sync-download-and-start-readiness-with-mocked-sync (async done (let [data-dir (node-helper/create-tmp-dir "db-worker-sync-cli") download-repo "sync-download-graph" @@ -390,7 +390,7 @@ (is false (str "unexpected error: " e)) (done))))))) -(deftest test-cli-sync-upload-with-mocked-worker-bootstrap +(deftest ^:long test-cli-sync-upload-with-mocked-worker-bootstrap (async done (let [data-dir (node-helper/create-tmp-dir "db-worker-sync-upload-cli") upload-repo "sync-upload-graph" @@ -432,7 +432,7 @@ (is false (str "unexpected error: " e)))) (p/finally done))))) -(deftest test-cli-sync-upload-followed-by-graph-info-shows-graph-uuid-test +(deftest ^:long test-cli-sync-upload-followed-by-graph-info-shows-graph-uuid-test (async done (let [data-dir (node-helper/create-tmp-dir "db-worker-sync-upload-info-cli") upload-repo "sync-upload-graph-info" @@ -2209,6 +2209,37 @@ (is false (str "unexpected error: " e)) (done))))))) +(deftest ^:long test-cli-list-page-fields-filter + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker")] + (-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") + _ (fs/writeFileSync cfg-path "{:output-format :json}") + _ (run-cli ["graph" "create" "--graph" "fields-graph"] data-dir cfg-path) + _ (run-cli ["upsert" "page" "--page" "FieldTest"] data-dir cfg-path) + all-result (run-cli ["list" "page"] data-dir cfg-path) + all-payload (parse-json-output all-result) + filtered-result (run-cli ["list" "page" "--fields" "title"] data-dir cfg-path) + filtered-payload (parse-json-output filtered-result) + human-filtered-result (run-cli ["list" "page" "--fields" "title" "--output" "human"] data-dir cfg-path) + first-all (first (get-in all-payload [:data :items])) + first-filtered (first (get-in filtered-payload [:data :items])) + _ (stop-repo! data-dir cfg-path "fields-graph")] + (is (= 0 (:exit-code all-result))) + (is (= 0 (:exit-code filtered-result))) + (is (contains? first-all :title)) + (is (contains? first-all :updated-at)) + (is (contains? first-filtered :title)) + (is (not (contains? first-filtered :updated-at)) + "--fields title should exclude other fields") + (is (= 0 (:exit-code human-filtered-result))) + (is (string/includes? (:output human-filtered-result) "TITLE")) + (is (not (string/includes? (:output human-filtered-result) "UPDATED-AT")) + "--fields title human output should not show UPDATED-AT column") + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + (deftest ^:long test-cli-show-page-block-by-id-and-uuid (async done (let [data-dir (node-helper/create-tmp-dir "db-worker")]