This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch file-handler2
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 442288e1494d72c3e061029fea4644bddb57a34d
Author: Beto Dealmeida <[email protected]>
AuthorDate: Wed Nov 19 14:33:12 2025 -0500

    feat: file handler for CSV/XSL
---
 superset-frontend/src/pages/FileHandler/index.tsx | 128 ++++++++++++++++++++++
 superset-frontend/src/pwa-manifest.json           |  30 +++++
 superset-frontend/src/service-worker.ts           |  30 +++++
 superset-frontend/src/views/routes.tsx            |   8 ++
 superset-frontend/webpack.config.js               |   7 +-
 superset/templates/superset/spa.html              |  14 ++-
 superset/views/core.py                            |  14 +++
 7 files changed, 229 insertions(+), 2 deletions(-)

diff --git a/superset-frontend/src/pages/FileHandler/index.tsx 
b/superset-frontend/src/pages/FileHandler/index.tsx
new file mode 100644
index 0000000000..960fcf0add
--- /dev/null
+++ b/superset-frontend/src/pages/FileHandler/index.tsx
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useEffect, useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { t } from '@superset-ui/core';
+import { Loading } from '@superset-ui/core/components';
+import UploadDataModal from 'src/features/databases/UploadDataModel';
+import withToasts from 'src/components/MessageToasts/withToasts';
+
+interface LaunchParams {
+  readonly files: readonly FileSystemFileHandle[];
+}
+
+interface FileHandlerProps {
+  addDangerToast: (msg: string) => void;
+  addSuccessToast: (msg: string) => void;
+}
+
+const FileHandler = ({ addDangerToast, addSuccessToast }: FileHandlerProps) => 
{
+  const history = useHistory();
+  const [uploadFile, setUploadFile] = useState<File | null>(null);
+  const [uploadType, setUploadType] = useState<
+    'csv' | 'excel' | 'columnar' | null
+  >(null);
+  const [showModal, setShowModal] = useState(false);
+  const [allowedExtensions, setAllowedExtensions] = useState<string[]>([]);
+
+  useEffect(() => {
+    const handleFileLaunch = async () => {
+      if (!('launchQueue' in window)) {
+        addDangerToast(
+          t(
+            'File handling is not supported in this browser. Please use a 
modern browser like Chrome or Edge.',
+          ),
+        );
+        history.push('/superset/welcome/');
+        return;
+      }
+
+      const launchQueue = (window as any).launchQueue;
+      launchQueue.setConsumer(async (launchParams: LaunchParams) => {
+        if (!launchParams.files || launchParams.files.length === 0) {
+          history.push('/superset/welcome/');
+          return;
+        }
+
+        try {
+          const fileHandle = launchParams.files[0];
+          const file = await fileHandle.getFile();
+          const fileName = file.name.toLowerCase();
+
+          let type: 'csv' | 'excel' | 'columnar' | null = null;
+          let extensions: string[] = [];
+
+          if (fileName.endsWith('.csv')) {
+            type = 'csv';
+            extensions = ['.csv'];
+          } else if (fileName.endsWith('.xls') || fileName.endsWith('.xlsx')) {
+            type = 'excel';
+            extensions = ['.xls', '.xlsx'];
+          } else if (fileName.endsWith('.parquet')) {
+            type = 'columnar';
+            extensions = ['.parquet'];
+          } else {
+            addDangerToast(
+              t(
+                'Unsupported file type. Please use CSV, Excel, or Columnar 
files.',
+              ),
+            );
+            history.push('/superset/welcome/');
+            return;
+          }
+
+          setUploadFile(file);
+          setUploadType(type);
+          setAllowedExtensions(extensions);
+          setShowModal(true);
+        } catch (error) {
+          console.error('Error handling file launch:', error);
+          addDangerToast(t('Failed to open file. Please try again.'));
+          history.push('/superset/welcome/');
+        }
+      });
+    };
+
+    handleFileLaunch();
+  }, [history, addDangerToast]);
+
+  const handleModalClose = () => {
+    setShowModal(false);
+    setUploadFile(null);
+    setUploadType(null);
+    history.push('/superset/welcome/');
+  };
+
+  if (!uploadFile || !uploadType) {
+    return <Loading />;
+  }
+
+  return (
+    <UploadDataModal
+      show={showModal}
+      onHide={handleModalClose}
+      allowedExtensions={allowedExtensions}
+      type={uploadType}
+      addDangerToast={addDangerToast}
+      addSuccessToast={addSuccessToast}
+    />
+  );
+};
+
+export default withToasts(FileHandler);
diff --git a/superset-frontend/src/pwa-manifest.json 
b/superset-frontend/src/pwa-manifest.json
new file mode 100644
index 0000000000..07805a88ad
--- /dev/null
+++ b/superset-frontend/src/pwa-manifest.json
@@ -0,0 +1,30 @@
+{
+  "name": "Apache Superset",
+  "short_name": "Superset",
+  "description": "Modern data exploration and visualization platform",
+  "start_url": "/superset/welcome/",
+  "display": "standalone",
+  "background_color": "#ffffff",
+  "theme_color": "#20a7c9",
+  "icons": [
+    {
+      "src": "/static/assets/images/superset-logo-horiz.png",
+      "sizes": "any",
+      "type": "image/png",
+      "purpose": "any"
+    }
+  ],
+  "file_handlers": [
+    {
+      "action": "/file-handler",
+      "accept": {
+        "text/csv": [".csv"],
+        "application/vnd.ms-excel": [".xls"],
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
+          ".xlsx"
+        ],
+        "application/vnd.apache.parquet": [".parquet"]
+      }
+    }
+  ]
+}
diff --git a/superset-frontend/src/service-worker.ts 
b/superset-frontend/src/service-worker.ts
new file mode 100644
index 0000000000..0e0bbb5763
--- /dev/null
+++ b/superset-frontend/src/service-worker.ts
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare const self: ServiceWorkerGlobalScope;
+
+self.addEventListener('install', (event: ExtendableEvent) => {
+  self.skipWaiting();
+});
+
+self.addEventListener('activate', (event: ExtendableEvent) => {
+  event.waitUntil(self.clients.claim());
+});
+
+export {};
diff --git a/superset-frontend/src/views/routes.tsx 
b/superset-frontend/src/views/routes.tsx
index 0cb0ba9473..eb6dbd05dd 100644
--- a/superset-frontend/src/views/routes.tsx
+++ b/superset-frontend/src/views/routes.tsx
@@ -178,6 +178,10 @@ const UserRegistrations = lazy(
     ),
 );
 
+const FileHandler = lazy(
+  () => import(/* webpackChunkName: "FileHandler" */ 'src/pages/FileHandler'),
+);
+
 type Routes = {
   path: string;
   Component: ComponentType;
@@ -206,6 +210,10 @@ export const routes: Routes = [
     path: '/superset/welcome/',
     Component: Home,
   },
+  {
+    path: '/file-handler',
+    Component: FileHandler,
+  },
   {
     path: '/dashboard/list/',
     Component: DashboardList,
diff --git a/superset-frontend/webpack.config.js 
b/superset-frontend/webpack.config.js
index 03e48cc3c5..5be2395759 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -139,7 +139,11 @@ const plugins = [
   }),
 
   new CopyPlugin({
-    patterns: ['package.json', { from: 'src/assets/images', to: 'images' }],
+    patterns: [
+      'package.json',
+      { from: 'src/assets/images', to: 'images' },
+      { from: 'src/pwa-manifest.json', to: 'pwa-manifest.json' },
+    ],
   }),
 
   // static pages
@@ -300,6 +304,7 @@ const config = {
     menu: addPreamble('src/views/menu.tsx'),
     spa: addPreamble('/src/views/index.tsx'),
     embedded: addPreamble('/src/embedded/index.tsx'),
+    'service-worker': path.join(APP_DIR, '/src/service-worker.ts'),
   },
   cache: {
     type: 'filesystem', // Enable filesystem caching
diff --git a/superset/templates/superset/spa.html 
b/superset/templates/superset/spa.html
index 8ce70725c2..e8e71015b6 100644
--- a/superset/templates/superset/spa.html
+++ b/superset/templates/superset/spa.html
@@ -28,7 +28,9 @@
       {% endblock %}
     </title>
 
-    {% block head_meta %}{% endblock %}
+    {% block head_meta %}
+      <link rel="manifest" href="{{ assets_prefix 
}}/static/assets/pwa-manifest.json">
+    {% endblock %}
 
     <style>
       body {
@@ -73,6 +75,16 @@
     />
     {% block head_js %}
       {% include "head_custom_extra.html" %}
+      <script nonce="{{ macros.get_nonce() }}">
+        if ('serviceWorker' in navigator) {
+          window.addEventListener('load', function() {
+            navigator.serviceWorker.register('{{ assets_prefix 
}}/static/assets/service-worker.js')
+              .catch(function(err) {
+                console.error('Service Worker registration failed:', err);
+              });
+          });
+        }
+      </script>
     {% endblock %}
   </head>
 
diff --git a/superset/views/core.py b/superset/views/core.py
index ced2078b14..af1bfef6dc 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -908,6 +908,20 @@ class Superset(BaseSupersetView):
 
         return self.render_app_template(extra_bootstrap_data=payload)
 
+    @event_logger.log_this
+    @expose("/file-handler")
+    def file_handler(self) -> FlaskResponse:
+        """File handler page for PWA file handling"""
+        if not g.user or not get_user_id():
+            return redirect_to_login()
+
+        payload = {
+            "user": bootstrap_user_data(g.user, include_perms=True),
+            "common": common_bootstrap_payload(),
+        }
+
+        return self.render_app_template(extra_bootstrap_data=payload)
+
     @has_access
     @event_logger.log_this
     @expose("/sqllab/history/", methods=("GET",))

Reply via email to