Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make format export optional #40606

Merged
merged 31 commits into from
Mar 28, 2024
Merged

Make format export optional #40606

merged 31 commits into from
Mar 28, 2024

Conversation

adam-james-v
Copy link
Contributor

@adam-james-v adam-james-v commented Mar 25, 2024

Fixes: #40420
Related areas:

  • direct download on Questions
  • download results on Dashboard Cards
  • formatting in alert/subscription attachments

Before this PR:

49 Introduced a nice change: exports now have formatting that matches the App. That is, visualization settings provided by admins and the users are respected in exports. We consider this a feature/improvement.

This did, however, change default outputs which is a surprise to some users who have built workflows and expectations around the minimal formatting that was provided in our exports previously. There was no way to opt-out of this new formatting except to recreate questions with visualization/column formatting explicitly set up to provide the desired output. This manual recreation isn't optimal in all cases.

After this PR:

This PR provides a URL parameter format_rows=false that allows API users to specify whether or not the 'App formatting' (what you see in the UI) is applied to the requested export.

All of the places the format_rows=false option can be used:

  • /api/card/:card-id/query/:export-format
  • /api/dataset/:export-format
  • /api/dashboard/:dash-id/dashcard/:dashcard-id/card/:card-id/query/:export-format
  • /api/public/card/:uuid/query/:export-format
  • /api/embed/dashboard/:uuid/dashcard/:dashcard-id/card/:card-id/:export-format

Loom showing it working in various spots:
https://www.loom.com/share/35c135f846ff43958f8e43106ccdc2da?sid=c3c36612-c997-455e-90fe-e942e4ce34df

Here's a simple Clojure repl session/screenshot to illustrate the difference:
image

(ns tickets.unformatted
  (:require
   [clojure.data.csv :as csv]
   [clojure.test :refer :all]
   [toucan2.tools.with-temp :as t2.with-temp]))

;; Several endpoints have been altered to accept the boolean format_export parameter.
;; Here's the card endpoint for simplicity:

(defn explore
  []
  (let [q {:database (mt/id)
           :type     :native
           :native   {:query "SELECT 2000 AS number, '2024-03-26'::DATE AS date;"}}]
    (t2.with-temp/with-temp [Card {card-id :id} {:display :table :dataset_query q}]
      {:formatted
       (csv/read-csv (mt/user-http-request
                      :crowberto :post 200
                      (format "card/%d/query/csv" card-id)))
       :raw
       (csv/read-csv (mt/user-http-request
                      :crowberto :post 200
                      (format "card/%d/query/csv?format_rows=false" card-id)))})))

This change, paired with some frontend changes in: #40643 should give the option to turn off this formatting to users who need them in specific circumstances.

Since the format_export is defaulted to true in the middleware, we should see the improved formatting as the default everywhere, and turning it off becomes a specific choice users must make.

Format Rows as Middleware

I implemented this as a simple middleware. I think it makes sense alongside other middleware flags like :format-rows (which might be a good candidate for this :format_export to be merged into later), and process-viz-settings?. I considered passing a key into the qp.si/streaming-results-writer via viz-settings, but that felt like an abuse of the viz-settings map, and could be confusing as :format_export is not a key we intend to store in the app db, but is rather just a flag for the expected export behaviour during a single execution.

Todo:

  • allow format_rows option to be used in subscriptions too
  • fix default to 'true' (I think I mixed things up with Clojure's nil/false behaviour)
  • apply the toggle to the xlsx exporter as well NOTE: xlsx formatting was already applied in prior versions, so leaving it alone for now
  • add tests confirming correct behaviour for each of the 3 affected endpoints
  • provide some examples in this PR
  • explain the technical reason for how its implemented (below the fold in this PR)
  • apply option to the related areas (tbd on exactly what this looks like) follow on frontend work in Make format export optional (FE part) #40643

Formatting on exports can be turned off via a middleware key:

`:format-export?` which will control if the 'app style' formatting will be applied to the exported results.
`:format-export? false` will return the file as in previous Metabase versions.
Should still default to formatted for v. 49, as the formatting is intended as a feature. This default value should be
provided in a central place, so is added to the middleware and is therefore not necessary for the API to provide a default.
@metabase-bot metabase-bot bot added the .Team/DashViz Dashboard and Viz team label Mar 25, 2024
@adam-james-v adam-james-v added the backport Automatically create PR on current release branch on merge label Mar 26, 2024
@adam-james-v adam-james-v marked this pull request as ready for review March 26, 2024 20:19
Copy link

replay-io bot commented Mar 26, 2024

Status Complete ↗︎
Commit da7e634
Results
⚠️ 2 Flaky
2387 Passed

@adam-james-v adam-james-v requested a review from a team March 26, 2024 21:22
@dpsutton
Copy link
Contributor

We already have a notion of :format-rows? and I worry that adding format-export? is adding extra knobs. Is it possible to unify these?

user=> (ns foo)
nil
foo=> (require '[clojure.data.csv :as csv]
               '[metabase.query-processor :as qp])
nil
foo=> (for [format? [true false]]
        (let [q {:database 1
                 :type     :native
                 :native   {:query "SELECT 2000 AS number, '2024-03-26'::DATE AS date;"}
                 :middleware {:format-rows? format?}}
              sw (java.io.StringWriter.)]
          (->> (qp/process-query q)
               :data :rows (csv/write-csv sw))
          [format? (str sw)]))
([true "2000,2024-03-26T00:00:00Z\n"]
 [false "2000,2024-03-26\n"])

@adam-james-v
Copy link
Contributor Author

adam-james-v commented Mar 26, 2024

We already have a notion of :format-rows? and I worry that adding format-export? is adding extra knobs. Is it possible to unify these?

user=> (ns foo)
nil
foo=> (require '[clojure.data.csv :as csv]
               '[metabase.query-processor :as qp])
nil
foo=> (for [format? [true false]]
        (let [q {:database 1
                 :type     :native
                 :native   {:query "SELECT 2000 AS number, '2024-03-26'::DATE AS date;"}
                 :middleware {:format-rows? format?}}
              sw (java.io.StringWriter.)]
          (->> (qp/process-query q)
               :data :rows (csv/write-csv sw))
          [format? (str sw)]))
([true "2000,2024-03-26T00:00:00Z\n"]
 [false "2000,2024-03-26\n"])

I think that makes sense. I've changed it to unify on :format-rows. I think this will reveal a few test failures though, which might take a minute to sort out properly.

Copy link
Member

@noahmoss noahmoss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable to me.

src/metabase/legacy_mbql/schema.cljc Outdated Show resolved Hide resolved
src/metabase/api/card.clj Show resolved Hide resolved
src/metabase/legacy_mbql/schema.cljc Outdated Show resolved Hide resolved
src/metabase/query_processor/streaming/csv.clj Outdated Show resolved Hide resolved
src/metabase/query_processor/streaming/json.clj Outdated Show resolved Hide resolved
* Refactor download URL builder code

Need to handle query `params` and request `body` separately

* Export `useHover` from `metabase/ui`

* Add unformatted export for dashboard cards

* Add unformatted export for saved questions

* Fix POST body encoding

* Add unformatted export for ad-hoc questions

* Ensure `format_export` is always false for Excel

* Add unformatted export to public questions

* Add unformatted export to embedded questions and dashboards

* Fix behavior for PNG format

* Add tests for `QueryDownloadPopover`

* Fix e2e downloads helper (allow URL query params)

* Rework alt key handling

* Add e2e test

* Switch to `format_rows` instead of `format_export`

* Add unformatted export option to subscriptions

* Fix `isHoldingAltKey` initial state

* Rework e2e test to use Total column

* Fix initial checkbox state for subscriptions

* Use "Alt" in tooltip label for Windows/Linux

* Show the subscriptions toggle only for CSV attachments

---------

Co-authored-by: adam-james <21064735+adam-james-v@users.noreply.github.com>
Copy link

github-actions bot commented Mar 28, 2024

Codenotify: Notifying subscribers in CODENOTIFY files for diff e63d82e...da7e634.

Notify File(s)
@kdoh frontend/src/metabase/ui/index.ts
src/metabase/email/messages.clj
@ranquild frontend/src/metabase/sharing/components/EmailAttachmentPicker.jsx
frontend/src/metabase/sharing/components/SharingSidebar/SharingSidebar.jsx

@adam-james-v adam-james-v enabled auto-merge (squash) March 28, 2024 17:07
@adam-james-v adam-james-v merged commit 347c5ef into master Mar 28, 2024
107 checks passed
@adam-james-v adam-james-v deleted the make-format-export-optional branch March 28, 2024 17:59
Copy link

@adam-james-v Did you forget to add a milestone to the issue for this PR? When and where should I add a milestone?

adam-james-v added a commit that referenced this pull request Mar 28, 2024
* Make Formatting on Exports Optional

Formatting on exports can be turned off via a middleware key:

`:format-export?` which will control if the 'app style' formatting will be applied to the exported results.
`:format-export? false` will return the file as in previous Metabase versions.

* format_export's default value provided in the QP middleware

Should still default to formatted for v. 49, as the formatting is intended as a feature. This default value should be
provided in a central place, so is added to the middleware and is therefore not necessary for the API to provide a default.

* weird indentation

* Add format_export param to dashboard dashcard query endpoint

* Add format_export option to embedded dashboard downloads endpoint

* Move default true value to the streaming-results-writer impls

* Default true but for real this time

* Add format_export to the public Question endpoint

* Add test to card api

* Dashboard exports endpoint test

* Add test to dashboard embeds

Test still fails and I don't know why yet

* the dangers of println debugging....

Well, I was passing a string false the whole time. Turns out the problem was me

* Public Card downloads endpoint now has format_export and test

* export_format also works for adhoc queries using api/dataset

* Let defendpoint do the boolean parsing

* Embed endpoint should now work too.

* Unify format-rows and format-export middlewares

* Sneaky endpoint!

* In xlsx, always parse temporal strings so that viz-settings formatting is applied

* Snuck in an incomplete change by accident. removed it.

* Can now specify format/no format on subscriptions

* Fix and move migration

* default format rows to true in attachments

* Ok, I think that should actually work for subscriptions.

* Change endpoints to use format_rows instead of confusing format_export

* format_rows everywhere

* Fix more test failures

* redef'd function had wrong signature

* Make format export optional (FE part) (#40643)

* Refactor download URL builder code

Need to handle query `params` and request `body` separately

* Export `useHover` from `metabase/ui`

* Add unformatted export for dashboard cards

* Add unformatted export for saved questions

* Fix POST body encoding

* Add unformatted export for ad-hoc questions

* Ensure `format_export` is always false for Excel

* Add unformatted export to public questions

* Add unformatted export to embedded questions and dashboards

* Fix behavior for PNG format

* Add tests for `QueryDownloadPopover`

* Fix e2e downloads helper (allow URL query params)

* Rework alt key handling

* Add e2e test

* Switch to `format_rows` instead of `format_export`

* Add unformatted export option to subscriptions

* Fix `isHoldingAltKey` initial state

* Rework e2e test to use Total column

* Fix initial checkbox state for subscriptions

* Use "Alt" in tooltip label for Windows/Linux

* Show the subscriptions toggle only for CSV attachments

---------

Co-authored-by: adam-james <21064735+adam-james-v@users.noreply.github.com>

* Address review feedback

---------

Co-authored-by: Anton Kulyk <kuliks.anton@gmail.com>
adam-james-v added a commit that referenced this pull request Mar 29, 2024
* Make Formatting on Exports Optional

Formatting on exports can be turned off via a middleware key:

`:format-export?` which will control if the 'app style' formatting will be applied to the exported results.
`:format-export? false` will return the file as in previous Metabase versions.

* format_export's default value provided in the QP middleware

Should still default to formatted for v. 49, as the formatting is intended as a feature. This default value should be
provided in a central place, so is added to the middleware and is therefore not necessary for the API to provide a default.

* weird indentation

* Add format_export param to dashboard dashcard query endpoint

* Add format_export option to embedded dashboard downloads endpoint

* Move default true value to the streaming-results-writer impls

* Default true but for real this time

* Add format_export to the public Question endpoint

* Add test to card api

* Dashboard exports endpoint test

* Add test to dashboard embeds

Test still fails and I don't know why yet

* the dangers of println debugging....

Well, I was passing a string false the whole time. Turns out the problem was me

* Public Card downloads endpoint now has format_export and test

* export_format also works for adhoc queries using api/dataset

* Let defendpoint do the boolean parsing

* Embed endpoint should now work too.

* Unify format-rows and format-export middlewares

* Sneaky endpoint!

* In xlsx, always parse temporal strings so that viz-settings formatting is applied

* Snuck in an incomplete change by accident. removed it.

* Can now specify format/no format on subscriptions

* Fix and move migration

* default format rows to true in attachments

* Ok, I think that should actually work for subscriptions.

* Change endpoints to use format_rows instead of confusing format_export

* format_rows everywhere

* Fix more test failures

* redef'd function had wrong signature

* Make format export optional (FE part) (#40643)

* Refactor download URL builder code

Need to handle query `params` and request `body` separately

* Export `useHover` from `metabase/ui`

* Add unformatted export for dashboard cards

* Add unformatted export for saved questions

* Fix POST body encoding

* Add unformatted export for ad-hoc questions

* Ensure `format_export` is always false for Excel

* Add unformatted export to public questions

* Add unformatted export to embedded questions and dashboards

* Fix behavior for PNG format

* Add tests for `QueryDownloadPopover`

* Fix e2e downloads helper (allow URL query params)

* Rework alt key handling

* Add e2e test

* Switch to `format_rows` instead of `format_export`

* Add unformatted export option to subscriptions

* Fix `isHoldingAltKey` initial state

* Rework e2e test to use Total column

* Fix initial checkbox state for subscriptions

* Use "Alt" in tooltip label for Windows/Linux

* Show the subscriptions toggle only for CSV attachments

---------



* Address review feedback

---------

Co-authored-by: Adam James <adam.vermeer2@gmail.com>
Co-authored-by: Anton Kulyk <kuliks.anton@gmail.com>
@dmeremyanin
Copy link

Hey @adam-james-v, sorry for disturbing. Shouldn't the export formatting be something that needs to be turned on in the settings instead of being on by default? Or at least it would be great to turn it off in order to get back the pre-v49 behaviour globally. It's not always possible to update all the URLs that point to Metabase and using it as an API endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport Automatically create PR on current release branch on merge .Team/DashViz Dashboard and Viz team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should not use formatting for JSON results downloads
6 participants