From 04ffe6269a4ee3bd4a2b3e042d75268fcde9d254 Mon Sep 17 00:00:00 2001
From: Leah Tacke genannt Unterberg <leah.tgu@pads.rwth-aachen.de>
Date: Wed, 9 Apr 2025 17:21:37 +0200
Subject: [PATCH] the app is runnable again

---
 docker-compose.yml                            |   4 +-
 helm/superset/Chart.lock                      |   8 +-
 helm/superset/Chart.yaml                      |  12 +-
 superset-frontend/package-lock.json           |   2 +-
 .../mitmDatasets/MitMDatasetModal/index.tsx   |   2 +-
 .../UploadMitMDatasetModal/index.tsx          |  60 +++++----
 .../mitmDatasets/asyncExternalService.ts      |   4 +-
 .../src/pages/MitMDatasetList/index.tsx       |  61 +++++----
 superset/cli/importexport.py                  |  85 +++++++++++-
 .../mitm/mitm_dataset/importers/dispatcher.py |  72 +++++++++++
 .../mitm_dataset/importers/v1/__init__.py     |   2 +-
 superset/initialization/__init__.py           |  26 ++--
 .../2025-03-20_14-24_8e7de5b14153_.py         |  90 -------------
 ...0_14-24_8e7de5b14153_added_mitm_dataset.py | 121 ++++++++++++++++++
 superset/models/mitm.py                       |  18 +++
 tests/requests/list_resources.http            |  33 +++++
 tests/requests/mitm_dataset_api.http          |   2 +
 tests/requests/mitm_service_call.http         |  13 +-
 18 files changed, 434 insertions(+), 181 deletions(-)
 create mode 100644 superset/commands/mitm/mitm_dataset/importers/dispatcher.py
 delete mode 100644 superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_.py
 create mode 100644 superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_added_mitm_dataset.py
 create mode 100644 tests/requests/list_resources.http

diff --git a/docker-compose.yml b/docker-compose.yml
index f74147e15e..d8f3a39287 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -273,10 +273,10 @@ services:
 
   superset-mitm-service:
     build:
-      context: "C:/Users/leah/PycharmProjects/superset-mitm-service"
+      context: "https://git-ce.rwth-aachen.de/machine-data/superset-mitm-service.git"
+      # context: "C:/Users/leah/PycharmProjects/superset-mitm-service"
       pull: true
       no_cache: true
-      # context: "https://git-ce.rwth-aachen.de/machine-data/superset-mitm-service.git"
     container_name: superset_mitm_service
     env_file:
       - path: docker/.env-mitm # default
diff --git a/helm/superset/Chart.lock b/helm/superset/Chart.lock
index aab1b386d4..b8cc122bb9 100644
--- a/helm/superset/Chart.lock
+++ b/helm/superset/Chart.lock
@@ -1,12 +1,12 @@
 dependencies:
 - name: postgresql
   repository: oci://registry-1.docker.io/bitnamicharts
-  version: 12.1.6
+  version: 13.4.4
 - name: redis
   repository: oci://registry-1.docker.io/bitnamicharts
   version: 17.9.4
 - name: superset-mitm-service
   repository: oci://registry-1.docker.io/leahtgu
-  version: 0.1.0
-digest: sha256:39aa1b51029921ce8517209ba4ec5c20fdadd9b879b616fc5a2e30920bee8246
-generated: "2025-03-19T10:31:53.7889567+01:00"
+  version: 0.1.1
+digest: sha256:7453ef9a2931ded80c999853692344ec8c3e779e999f1b8120d86d108c1fef14
+generated: "2025-04-09T14:32:24.7839858+02:00"
diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index 6d60e3f368..ca0403acb7 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -17,19 +17,15 @@
 apiVersion: v2
 appVersion: "4.1.1"
 description: Apache Superset is a modern, enterprise-ready business intelligence web application
-name: superset
+name: mitm-superset
 icon: https://artifacthub.io/image/68c1d717-0e97-491f-b046-754e46f46922@2x
 home: https://superset.apache.org/
 keywords:
   - business intelligence
   - data science
 sources:
-  - https://github.com/apache/superset
-maintainers:
-  - name: craig-rueda
-    email: craig@craigrueda.com
-    url: https://github.com/craig-rueda
-version: 0.14.1
+  - https://git-ce.rwth-aachen.de/machine-data/mitm-superset
+version: 0.1.0
 dependencies:
   - name: postgresql
     version: 13.4.4
@@ -40,6 +36,6 @@ dependencies:
     repository: oci://registry-1.docker.io/bitnamicharts
     condition: redis.enabled
   - name: superset-mitm-service
-    version: 0.1.0
+    version: 0.1.1
     repository: oci://registry-1.docker.io/leahtgu
     condition: superset-mitm-service.enabled
diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 1f9c50d9ad..7391263d43 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -50731,7 +50731,7 @@
       "version": "0.20.3",
       "license": "Apache-2.0",
       "dependencies": {
-        "@types/react-redux": "^7.1.10",
+        "@types/react-redux": "^7.1.34",
         "d3-array": "^1.2.0",
         "dayjs": "^1.11.13",
         "lodash": "^4.17.21"
diff --git a/superset-frontend/src/features/mitmDatasets/MitMDatasetModal/index.tsx b/superset-frontend/src/features/mitmDatasets/MitMDatasetModal/index.tsx
index 6229be0388..a6c1243723 100644
--- a/superset-frontend/src/features/mitmDatasets/MitMDatasetModal/index.tsx
+++ b/superset-frontend/src/features/mitmDatasets/MitMDatasetModal/index.tsx
@@ -29,7 +29,7 @@ import {
   css,
 } from '@superset-ui/core';
 
-import Icons from 'src/components/Icons';
+import { Icons } from 'src/components/Icons';
 import Modal from 'src/components/Modal';
 import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
 import withToasts from 'src/components/MessageToasts/withToasts';
diff --git a/superset-frontend/src/features/mitmDatasets/UploadMitMDatasetModal/index.tsx b/superset-frontend/src/features/mitmDatasets/UploadMitMDatasetModal/index.tsx
index a08d2a1d0f..7ef54e1c88 100644
--- a/superset-frontend/src/features/mitmDatasets/UploadMitMDatasetModal/index.tsx
+++ b/superset-frontend/src/features/mitmDatasets/UploadMitMDatasetModal/index.tsx
@@ -10,8 +10,7 @@ import {
 } from '../../databases/UploadDataModel/styles';
 import { Form, Input, Select, Upload } from 'antd-v5';
 import { AsyncActionResult, useExternalService } from '../asyncExternalService';
-import { Button } from '../../../components';
-import { InboxOutlined, UploadOutlined } from '@ant-design/icons';
+import { InboxOutlined } from '@ant-design/icons';
 
 interface UploadMitMDatasetModalProps {
   addDangerToast: (msg: string) => void;
@@ -49,6 +48,7 @@ const UploadMitMDatasetModal: FunctionComponent<
     }: Pick<UploadMitMDatasetRequestFormData, 'mitm' | 'datasetName'>,
   ) => {
     addSuccessToast(t('Successfully uploaded \"%s\" (%s)', datasetName, mitm));
+    onClose();
   };
   const onFailure = (error: ClientErrorObject) => {
     setError(error);
@@ -92,11 +92,18 @@ const UploadMitMDatasetModal: FunctionComponent<
 
   const onFinish = (values: any) => {
     const fields = form.getFieldsValue();
+    const fileList = fields.fileList;
 
-    const mitmZip = fields.fileList[0]?.originFileObj;
+    if (!fileList?.[0]?.originFileObj) {
+      addDangerToast(t('Please select a file to upload'));
+      return;
+    }
 
-    const mitm = fields.mitm as MITM;
-    makeRequest({ datasetName: fields.datasetName, mitm, mitmZip });
+    makeRequest({
+      datasetName: fields.datasetName,
+      mitm: fields.mitm as MITM,
+      mitmZip: fileList[0].originFileObj,
+    });
   };
 
   const onClose = () => {
@@ -104,12 +111,11 @@ const UploadMitMDatasetModal: FunctionComponent<
     onHide();
   };
 
-  const extractFileList = (e: any) => {
-    console.log('Upload event:', e);
+  const normFile = (e: any) => {
     if (Array.isArray(e)) {
       return e;
     }
-    return e?.fileList;
+    return e?.fileList?.slice(-1) || []; // Keep only the latest file
   };
 
   return (
@@ -120,7 +126,7 @@ const UploadMitMDatasetModal: FunctionComponent<
         formStyles(theme),
       ]}
       primaryButtonLoading={isLoading}
-      name="database"
+      name="mitm-dataset"
       data-test="upload-modal"
       onHandledPrimaryAction={form.submit}
       onHide={onClose}
@@ -134,7 +140,7 @@ const UploadMitMDatasetModal: FunctionComponent<
         <Form.Item
           name="datasetName"
           label="Dataset Name"
-          rules={[{ required: true }]}
+          rules={[{ required: true, message: 'Please enter a dataset name' }]}
         >
           <Input />
         </Form.Item>
@@ -146,32 +152,24 @@ const UploadMitMDatasetModal: FunctionComponent<
           <Select
             placeholder="Select the MitM Name"
             allowClear
+            defaultValue={'MAED'}
             options={[{ name: 'MAED', value: 'MAED' }]}
           ></Select>
         </Form.Item>
         <Form.Item
-          name="upload"
+          name="fileList"
           label="Upload"
           valuePropName="fileList"
-          getValueFromEvent={extractFileList}
-          extra="longgggggggggggggggggggggggggggggggggg"
+          getValueFromEvent={normFile}
+          rules={[{ required: true, message: 'Please upload a file' }]}
         >
-          <Upload
-            name="logo"
-            accept=".zip,.maed,.mitm"
-            customRequest={() => false}
-          >
-            <Button icon={<UploadOutlined />}>Click to upload</Button>
-          </Upload>
-        </Form.Item>
-        <Form.Item label="Dragger">
-          <Form.Item
-            name="dragger"
-            valuePropName="fileList"
-            getValueFromEvent={extractFileList}
-            noStyle
-          >
-            <Upload.Dragger name="files">
+          <Upload>
+            <Upload.Dragger
+              name="file"
+              accept=".zip,.maed,.mitm"
+              beforeUpload={() => false}
+              maxCount={1}
+            >
               <p className="ant-upload-drag-icon">
                 <InboxOutlined />
               </p>
@@ -179,10 +177,10 @@ const UploadMitMDatasetModal: FunctionComponent<
                 Click or drag file to this area to upload
               </p>
               <p className="ant-upload-hint">
-                Support for a single or bulk upload.
+                Supported formats: .zip, .maed, .mitm
               </p>
             </Upload.Dragger>
-          </Form.Item>
+          </Upload>
         </Form.Item>
       </Form>
     </Modal>
diff --git a/superset-frontend/src/features/mitmDatasets/asyncExternalService.ts b/superset-frontend/src/features/mitmDatasets/asyncExternalService.ts
index 7296040a08..f32b5ab31b 100644
--- a/superset-frontend/src/features/mitmDatasets/asyncExternalService.ts
+++ b/superset-frontend/src/features/mitmDatasets/asyncExternalService.ts
@@ -102,7 +102,7 @@ export const asyncCall = async (requestConfig: RequestConfig) => {
       new Promise<AsyncActionResult | JsonObject>((resolve, reject) => {
         {
           if (response.status === 202) {
-            const jm: AsyncJobMetadata = json;
+            const jm = json as AsyncJobMetadata;
             waitForAsyncData(jm).then(resolve).catch(reject);
           } else {
             const err = parseErrorJson(json);
@@ -116,7 +116,7 @@ export const asyncCall = async (requestConfig: RequestConfig) => {
 export const asyncAction = async (
   requestConfig: RequestConfig,
 ): Promise<AsyncActionResult> => {
-  return asyncCall(requestConfig);
+  return asyncCall(requestConfig) as Promise<AsyncActionResult>;
 };
 
 type CallExternalServiceProps = {
diff --git a/superset-frontend/src/pages/MitMDatasetList/index.tsx b/superset-frontend/src/pages/MitMDatasetList/index.tsx
index a455f621ad..75210c86c0 100644
--- a/superset-frontend/src/pages/MitMDatasetList/index.tsx
+++ b/superset-frontend/src/pages/MitMDatasetList/index.tsx
@@ -16,40 +16,40 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { styled, SupersetClient, t } from '@superset-ui/core';
-import { FunctionComponent, useState, useMemo, useCallback } from 'react';
+import { css, styled, SupersetClient, t, useTheme } from '@superset-ui/core';
+import { FunctionComponent, useCallback, useMemo, useState } from 'react';
 import { useHistory } from 'react-router-dom';
 import rison from 'rison';
-import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
+import { createErrorHandler, createFetchRelated } from 'src/views/CRUD/utils';
 import { useListViewResource } from 'src/views/CRUD/hooks';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
 import DeleteModal from 'src/components/DeleteModal';
 import handleResourceExport from 'src/utils/export';
 import ListView, {
-  ListViewProps,
-  Filters,
   FilterOperator,
+  Filters,
+  ListViewProps,
 } from 'src/components/ListView';
 import Loading from 'src/components/Loading';
-import SubMenu, { SubMenuProps, ButtonProps } from 'src/features/home/SubMenu';
+import SubMenu, { ButtonProps, SubMenuProps } from 'src/features/home/SubMenu';
 import Owner from 'src/types/Owner';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import { Tooltip } from 'src/components/Tooltip';
-import Icons from 'src/components/Icons';
+import { Icons } from 'src/components/Icons';
 import FacePile from 'src/components/FacePile';
 import ImportModelsModal from 'src/components/ImportModal/index';
 import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
 import { MitMDataset } from 'src/features/mitmDatasets/types';
 
 import {
+  CONFIRM_OVERWRITE_MESSAGE,
   PAGE_SIZE,
-  SORT_BY,
   PASSWORDS_NEEDED_MESSAGE,
-  CONFIRM_OVERWRITE_MESSAGE,
+  SORT_BY,
 } from 'src/features/mitmDatasets/constants';
 import { ModifiedInfo } from 'src/components/AuditInfo';
-import MitMDatasetModal from "../../features/mitmDatasets/MitMDatasetModal";
-import UploadMitMDatasetModal from "../../features/mitmDatasets/UploadMitMDatasetModal";
+import MitMDatasetModal from '../../features/mitmDatasets/MitMDatasetModal';
+import UploadMitMDatasetModal from '../../features/mitmDatasets/UploadMitMDatasetModal';
 
 const Actions = styled.div`
   color: ${({ theme }) => theme.colors.grayscale.base};
@@ -93,6 +93,7 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
   user,
 }) => {
   const history = useHistory();
+  const theme = useTheme();
   const {
     state: {
       loading,
@@ -116,8 +117,6 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
   const [datasetCurrentlyEditing, setDatasetCurrentlyEditing] =
     useState<MitMDataset | null>(null);
 
-
-
   const [importingDataset, showImportModal] = useState<boolean>(false);
   const [uploadModalOpen, setUploadModalOpen] = useState<boolean>(false);
   const [preparingExport, setPreparingExport] = useState<boolean>(false);
@@ -183,11 +182,13 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
       );
 
   const handleBulkDatasetExport = (datasetsToExport: MitMDataset[]) => {
-    const ids = datasetsToExport.map(({ id }) => id);
-    handleResourceExport('mitm_dataset', ids, () => {
-      setPreparingExport(false);
-    });
-    setPreparingExport(true);
+    if (!!datasetsToExport) {
+      const ids = datasetsToExport.map(({ id }) => id);
+      handleResourceExport('mitm_dataset', ids, () => {
+        setPreparingExport(false);
+      });
+      setPreparingExport(true);
+    }
   };
 
   const columns = useMemo(
@@ -261,7 +262,7 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
                     className="action-button"
                     onClick={handleDelete}
                   >
-                    <Icons.Trash />
+                    <Icons.DeleteOutlined />
                   </span>
                 </Tooltip>
               )}
@@ -277,7 +278,7 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
                     className="action-button"
                     onClick={handleExport}
                   >
-                    <Icons.Share />
+                    <Icons.UploadOutlined />
                   </span>
                 </Tooltip>
               )}
@@ -299,7 +300,7 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
                     className={allowEdit ? 'action-button' : 'disabled'}
                     onClick={allowEdit ? handleEdit : undefined}
                   >
-                    <Icons.EditAlt />
+                    <Icons.EditOutlined />
                   </span>
                 </Tooltip>
               )}
@@ -415,7 +416,14 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
     buttonArr.push({
       name: (
         <>
-          <i className="fa fa-plus" /> {t('MitM Dataset')}{' '}
+          <Icons.PlusOutlined
+            iconColor={theme.colors.primary.light5}
+            iconSize="m"
+            css={css`
+              vertical-align: text-top;
+            `}
+          />
+          {t('MitM Dataset')}
         </>
       ),
       onClick: () => {
@@ -431,12 +439,12 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
           title={t('Upload dataset')}
           placement="bottomRight"
         >
-          <Icons.Import data-test="upload-button" />
+          <Icons.UploadOutlined data-test="upload-button" />
         </Tooltip>
       ),
       buttonStyle: 'link',
       onClick: () => setUploadModalOpen(true),
-    })
+    });
 
     buttonArr.push({
       name: (
@@ -445,7 +453,10 @@ const MitMDatasetList: FunctionComponent<MitMDatasetListProps> = ({
           title={t('Import datasets')}
           placement="bottomRight"
         >
-          <Icons.Import data-test="import-button" />
+          <Icons.DownloadOutlined
+            iconColor={theme.colors.primary.dark1}
+            data-test="import-button"
+          />
         </Tooltip>
       ),
       buttonStyle: 'link',
diff --git a/superset/cli/importexport.py b/superset/cli/importexport.py
index 50c8aa78a7..7721c299ee 100755
--- a/superset/cli/importexport.py
+++ b/superset/cli/importexport.py
@@ -201,6 +201,83 @@ def import_datasources(path: str, username: Optional[str] = "admin") -> None:
             sys.exit(1)
 
 
+@click.command()
+@with_appcontext
+@click.option(
+    "--path",
+    "-p",
+    required=True,
+    help="Path to a single ZIP file",
+)
+@click.option(
+    "--username",
+    "-u",
+    required=True,
+    help="Specify the user name to assign assets to",
+)
+def import_assets(path: str, username: Optional[str]) -> None:
+    """Import assets from ZIP file"""
+    # pylint: disable=import-outside-toplevel
+    from superset.commands.importers.v1.utils import get_contents_from_bundle
+    from superset.commands.importers.v1.assets import ImportAssetsCommand
+
+    if username is not None:
+        g.user = security_manager.find_user(username=username)
+    if is_zipfile(path):
+        with ZipFile(path) as bundle:
+            contents = get_contents_from_bundle(bundle)
+    else:
+        with open(path) as file:
+            contents = {path: file.read()}
+    try:
+        ImportAssetsCommand(contents, overwrite=True).run()
+    except Exception:  # pylint: disable=broad-except
+        logger.exception(
+            "There was an error when importing the asset(s), please check "
+            "the exception traceback in the log"
+        )
+        sys.exit(1)
+
+
+@click.command()
+@with_appcontext
+@click.option(
+    "--path",
+    "-p",
+    required=True,
+    help="Path to a single ZIP file",
+)
+@click.option(
+    "--username",
+    "-u",
+    required=True,
+    help="Specify the user name to assign MitM Datasets to",
+)
+def import_mitm_datasets(path: str, username: Optional[str]) -> None:
+    """Import assets from ZIP file"""
+    # pylint: disable=import-outside-toplevel
+    from superset.commands.mitm.mitm_dataset.importers.dispatcher import \
+        ImportMitMDatasetsCommand
+    from superset.commands.importers.v1.utils import get_contents_from_bundle
+
+    if username is not None:
+        g.user = security_manager.find_user(username=username)
+    if is_zipfile(path):
+        with ZipFile(path) as bundle:
+            contents = get_contents_from_bundle(bundle)
+    else:
+        with open(path) as file:
+            contents = {path: file.read()}
+    try:
+        ImportMitMDatasetsCommand(contents, overwrite=True).run()
+    except Exception:  # pylint: disable=broad-except
+        logger.exception(
+            "There was an error when importing the MitM Dataset(s), please check "
+            "the exception traceback in the log"
+        )
+        sys.exit(1)
+
+
 @click.command()
 @with_appcontext
 @click.option(
@@ -290,7 +367,7 @@ def legacy_export_datasources(
     "--path",
     "-p",
     help="Path to a single JSON file or path containing multiple JSON "
-    "files to import (*.json)",
+         "files to import (*.json)",
 )
 @click.option(
     "--recursive",
@@ -337,7 +414,7 @@ def legacy_import_dashboards(path: str, recursive: bool, username: str) -> None:
     "--path",
     "-p",
     help="Path to a single YAML file or path containing multiple YAML "
-    "files to import (*.yaml or *.yml)",
+         "files to import (*.yaml or *.yml)",
 )
 @click.option(
     "--sync",
@@ -345,8 +422,8 @@ def legacy_import_dashboards(path: str, recursive: bool, username: str) -> None:
     "sync",
     default="",
     help="comma separated list of element types to synchronize "
-    'e.g. "metrics,columns" deletes metrics and columns in the DB '
-    "that are not specified in the YAML file",
+         'e.g. "metrics,columns" deletes metrics and columns in the DB '
+         "that are not specified in the YAML file",
 )
 @click.option(
     "--recursive",
diff --git a/superset/commands/mitm/mitm_dataset/importers/dispatcher.py b/superset/commands/mitm/mitm_dataset/importers/dispatcher.py
new file mode 100644
index 0000000000..c809eb7af2
--- /dev/null
+++ b/superset/commands/mitm/mitm_dataset/importers/dispatcher.py
@@ -0,0 +1,72 @@
+# 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 logging
+from typing import Any
+
+from marshmallow.exceptions import ValidationError
+
+from superset.commands.base import BaseCommand
+from . import v1
+from superset.commands.exceptions import CommandInvalidError
+from superset.commands.importers.exceptions import IncorrectVersionError
+
+logger = logging.getLogger(__name__)
+
+# list of different import formats supported; v0 should be last because
+# the files are not versioned
+command_versions = [
+    v1.ImportMitMDatasetsCommand,
+]
+
+
+class ImportMitMDatasetsCommand(BaseCommand):
+    """
+    Import MitM Datasets.
+
+    This command dispatches the import to different versions of the command
+    until it finds one that matches.
+    """
+
+    def __init__(self, contents: dict[str, str], *args: Any, **kwargs: Any):
+        self.contents = contents
+        self.args = args
+        self.kwargs = kwargs
+
+    def run(self) -> None:
+        # iterate over all commands until we find a version that can
+        # handle the contents
+        for version in command_versions:
+            command = version(self.contents, *self.args, **self.kwargs)
+            try:
+                command.run()
+                return
+            except IncorrectVersionError:
+                logger.debug("File not handled by command, skipping")
+            except (CommandInvalidError, ValidationError):
+                # found right version, but file is invalid
+                logger.info("Command failed validation")
+                raise
+            except Exception:
+                # validation succeeded but something went wrong
+                logger.exception("Error running import command")
+                raise
+
+        raise CommandInvalidError("Could not find a valid command to import file")
+
+    def validate(self) -> None:
+        pass
diff --git a/superset/commands/mitm/mitm_dataset/importers/v1/__init__.py b/superset/commands/mitm/mitm_dataset/importers/v1/__init__.py
index 4fba9bf05e..13e1253b59 100644
--- a/superset/commands/mitm/mitm_dataset/importers/v1/__init__.py
+++ b/superset/commands/mitm/mitm_dataset/importers/v1/__init__.py
@@ -39,7 +39,7 @@ class ImportMitMDatasetsCommand(ImportModelsCommand):
         # self.import_base_assets_command = ImportAssetsCommand(contents, *args, **kwargs)
 
     @staticmethod
-    def _import(configs: dict[str, Any], overwrite: bool = False) -> None:
+    def _import(configs: dict[str, Any], overwrite: bool = False, contents: dict[str, Any] | None = None) -> None:
         ImportAssetsCommand._import(configs)
 
         configs = deepcopy(configs)
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index e744dff472..2d8d64679b 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -286,15 +286,15 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         if feature_flag_manager.is_feature_enabled("MITM_SUPPORT"):
             from superset.views.mitm.views import MitMDatasetView
 
-            appbuilder.add_view(
-                MitMDatasetView,
-                "MitMDatasetView",
-                label=__("MitMDataset View"),
-                icon="fa-database",
-                category="",
-                category_icon="",
-                menu_cond=lambda: feature_flag_manager.is_feature_enabled('MITM_SUPPORT')
-            )
+            # appbuilder.add_view(
+            #     MitMDatasetView,
+            #     "MitMDatasetView",
+            #     label=__("MitMDataset View"),
+            #     icon="fa-database",
+            #     category="",
+            #     category_icon="",
+            #     menu_cond=lambda: feature_flag_manager.is_feature_enabled('MITM_SUPPORT')
+            # )
             appbuilder.add_link(
                 "MitMDatasets",
                 label=__("MitM Datasets"),
@@ -304,6 +304,14 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
                 category_icon="",
                 cond=lambda: feature_flag_manager.is_feature_enabled('MITM_SUPPORT')
             )
+            appbuilder.add_link(
+                "MitMExporter",
+                label=__("MitM Exporter"),
+                href="https://maed-exporter.cluster.iop.rwth-aachen.de/",
+                category="",
+                category_icon="",
+                cond=lambda: feature_flag_manager.is_feature_enabled('MITM_SUPPORT')
+            )
 
         ###################
 
diff --git a/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_.py b/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_.py
deleted file mode 100644
index d94de6540a..0000000000
--- a/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# 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.
-"""empty message
-
-Revision ID: 8e7de5b14153
-Revises: 32bf93dfe2a4
-Create Date: 2025-03-20 14:24:36.237603
-
-"""
-import sqlalchemy_utils
-
-# revision identifiers, used by Alembic.
-revision = '8e7de5b14153'
-down_revision = '32bf93dfe2a4'
-
-from alembic import op
-import sqlalchemy as sa
-
-
-
-def upgrade():
-    # ### commands auto generated by Alembic - please adjust! ###
-    op.create_table('mitm_datasets',
-    sa.Column('uuid', sqlalchemy_utils.types.uuid.UUIDType(), nullable=True),
-    sa.Column('created_on', sa.DateTime(), nullable=True),
-    sa.Column('changed_on', sa.DateTime(), nullable=True),
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('dataset_name', sa.String(length=255), nullable=False),
-    sa.Column('mitm', sa.String(length=127), nullable=False),
-    sa.Column('mitm_header', sa.JSON(), nullable=True),
-    sa.Column('database_id', sa.Integer(), nullable=False),
-    sa.Column('created_by_fk', sa.Integer(), nullable=True),
-    sa.Column('changed_by_fk', sa.Integer(), nullable=True),
-    sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ),
-    sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ),
-    sa.ForeignKeyConstraint(['database_id'], ['dbs.id'], ondelete='CASCADE'),
-    sa.PrimaryKeyConstraint('id'),
-    sa.UniqueConstraint('uuid')
-    )
-    op.create_table('mitm_dataset_dashboards',
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
-    sa.Column('dashboard_id', sa.Integer(), nullable=True),
-    sa.ForeignKeyConstraint(['dashboard_id'], ['dashboards.id'], ondelete='CASCADE'),
-    sa.ForeignKeyConstraint(['mitm_dataset_id'], ['mitm_datasets.id'], ondelete='CASCADE'),
-    sa.PrimaryKeyConstraint('id'),
-    sa.UniqueConstraint('mitm_dataset_id', 'dashboard_id')
-    )
-    op.create_table('mitm_dataset_slices',
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
-    sa.Column('slice_id', sa.Integer(), nullable=True),
-    sa.ForeignKeyConstraint(['mitm_dataset_id'], ['mitm_datasets.id'], ondelete='CASCADE'),
-    sa.ForeignKeyConstraint(['slice_id'], ['slices.id'], ondelete='CASCADE'),
-    sa.PrimaryKeyConstraint('id'),
-    sa.UniqueConstraint('mitm_dataset_id', 'slice_id')
-    )
-    op.create_table('mitm_dataset_tables',
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
-    sa.Column('table_id', sa.Integer(), nullable=True),
-    sa.ForeignKeyConstraint(['mitm_dataset_id'], ['mitm_datasets.id'], ondelete='CASCADE'),
-    sa.ForeignKeyConstraint(['table_id'], ['tables.id'], ondelete='CASCADE'),
-    sa.PrimaryKeyConstraint('id'),
-    sa.UniqueConstraint('mitm_dataset_id', 'table_id')
-    )
-    # ### end Alembic commands ###
-
-def __downgrade():pass
-
-def downgrade():
-    op.drop_table('mitm_dataset_tables')
-    op.drop_table('mitm_dataset_slices')
-    op.drop_table('mitm_dataset_dashboards')
-    op.drop_table('mitm_datasets')
-    # ### end Alembic commands ###
diff --git a/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_added_mitm_dataset.py b/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_added_mitm_dataset.py
new file mode 100644
index 0000000000..7f69b30711
--- /dev/null
+++ b/superset/migrations/versions/2025-03-20_14-24_8e7de5b14153_added_mitm_dataset.py
@@ -0,0 +1,121 @@
+# 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.
+"""added mitm dataset
+
+Revision ID: 8e7de5b14153
+Revises: 32bf93dfe2a4
+Create Date: 2025-03-20 14:24:36.237603
+
+"""
+import sqlalchemy_utils
+
+# revision identifiers, used by Alembic.
+revision = '8e7de5b14153'
+down_revision = '32bf93dfe2a4'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('mitm_datasets',
+                    sa.Column('uuid',
+                              sqlalchemy_utils.types.uuid.UUIDType(),
+                              nullable=True),
+                    sa.Column('created_on', sa.DateTime(), nullable=True),
+                    sa.Column('changed_on', sa.DateTime(), nullable=True),
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('dataset_name', sa.String(length=255), nullable=False),
+                    sa.Column('mitm', sa.String(length=127), nullable=False),
+                    sa.Column('mitm_header', sa.JSON(), nullable=True),
+                    sa.Column('database_id', sa.Integer(), nullable=False),
+                    sa.Column('created_by_fk', sa.Integer(), nullable=True),
+                    sa.Column('changed_by_fk', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ),
+                    sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ),
+                    sa.ForeignKeyConstraint(['database_id'],
+                                            ['dbs.id'],
+                                            ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('uuid')
+                    )
+    op.create_table('mitm_dataset_owners',
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
+                    sa.Column('user_id', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['user_id'],
+                                            ['ab_user.id'],
+                                            ondelete='CASCADE'),
+                    sa.ForeignKeyConstraint(['mitm_dataset_id'],
+                                            ['mitm_datasets.id'],
+                                            ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('mitm_dataset_id', 'user_id')
+                    )
+    op.create_table('mitm_dataset_dashboards',
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
+                    sa.Column('dashboard_id', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['dashboard_id'],
+                                            ['dashboards.id'],
+                                            ondelete='CASCADE'),
+                    sa.ForeignKeyConstraint(['mitm_dataset_id'],
+                                            ['mitm_datasets.id'],
+                                            ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('mitm_dataset_id', 'dashboard_id')
+                    )
+    op.create_table('mitm_dataset_slices',
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
+                    sa.Column('slice_id', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['mitm_dataset_id'],
+                                            ['mitm_datasets.id'],
+                                            ondelete='CASCADE'),
+                    sa.ForeignKeyConstraint(['slice_id'],
+                                            ['slices.id'],
+                                            ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('mitm_dataset_id', 'slice_id')
+                    )
+    op.create_table('mitm_dataset_tables',
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('mitm_dataset_id', sa.Integer(), nullable=True),
+                    sa.Column('table_id', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['mitm_dataset_id'],
+                                            ['mitm_datasets.id'],
+                                            ondelete='CASCADE'),
+                    sa.ForeignKeyConstraint(['table_id'],
+                                            ['tables.id'],
+                                            ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('mitm_dataset_id', 'table_id')
+                    )
+    # ### end Alembic commands ###
+
+
+def __downgrade(): pass
+
+
+def downgrade():
+    op.drop_table('mitm_dataset_tables')
+    op.drop_table('mitm_dataset_slices')
+    op.drop_table('mitm_dataset_dashboards')
+    op.drop_table('mitm_dataset_owners')
+    op.drop_table('mitm_datasets')
+    # ### end Alembic commands ###
diff --git a/superset/models/mitm.py b/superset/models/mitm.py
index a4a1786f8f..8efea8558b 100644
--- a/superset/models/mitm.py
+++ b/superset/models/mitm.py
@@ -11,6 +11,7 @@ from sqlalchemy import (
 from sqlalchemy.orm import relationship
 from sqlalchemy.schema import UniqueConstraint
 
+from superset import security_manager
 from superset.models.helpers import AuditMixinNullable, ImportExportMixin
 
 metadata = Model.metadata  # pylint: disable=no-member
@@ -45,6 +46,17 @@ mitm_dataset_dashboards = sa.Table(
     UniqueConstraint("mitm_dataset_id", "dashboard_id"),
 )
 
+mitm_dataset_owners = sa.Table(
+    "mitm_dataset_owners",
+    metadata,
+    Column("id", Integer, primary_key=True),
+    Column("mitm_dataset_id",
+           Integer,
+           ForeignKey("mitm_datasets.id", ondelete="CASCADE")),
+    Column("user_id", Integer, ForeignKey("ab_user.id", ondelete="CASCADE")),
+    UniqueConstraint("mitm_dataset_id", "user_id"),
+)
+
 
 class MitMDataset(Model, AuditMixinNullable, ImportExportMixin):
     """An ORM object that stores MitM Dataset related information"""
@@ -62,6 +74,12 @@ class MitMDataset(Model, AuditMixinNullable, ImportExportMixin):
     slices = relationship('Slice', secondary=mitm_dataset_slices)
     dashboards = relationship('Dashboard', secondary=mitm_dataset_dashboards)
 
+    owners = relationship(
+        security_manager.user_model,
+        secondary=mitm_dataset_owners,
+        passive_deletes=True,
+    )
+
     export_fields = ['id', 'dataset_name', 'mitm', 'mitm_header', 'database_id']
     export_parent = 'database'
     export_children = ['tables', 'slices', 'dashboards']
diff --git a/tests/requests/list_resources.http b/tests/requests/list_resources.http
new file mode 100644
index 0000000000..97bca7e470
--- /dev/null
+++ b/tests/requests/list_resources.http
@@ -0,0 +1,33 @@
+###
+
+// @name List DB
+// @no-log
+GET http://localhost:8088/api/v1/database/
+Authorization: Bearer {{jwt_token}}
+X-CSRFToken: {{csrf_token}}
+
+###
+
+// @name List Datasets
+// @no-log
+GET http://localhost:8088/api/v1/dataset/
+Authorization: Bearer {{jwt_token}}
+X-CSRFToken: {{csrf_token}}
+
+###
+
+// @name List Charts
+// @no-log
+GET http://localhost:8088/api/v1/chart/
+Authorization: Bearer {{jwt_token}}
+X-CSRFToken: {{csrf_token}}
+
+###
+
+// @name List Dashboards
+// @no-log
+GET http://localhost:8088/api/v1/dashboard/
+Authorization: Bearer {{jwt_token}}
+X-CSRFToken: {{csrf_token}}
+
+###
diff --git a/tests/requests/mitm_dataset_api.http b/tests/requests/mitm_dataset_api.http
index 6d543cf832..e7150e771c 100644
--- a/tests/requests/mitm_dataset_api.http
+++ b/tests/requests/mitm_dataset_api.http
@@ -15,6 +15,7 @@ X-CSRFToken: {{csrf_token}}
 ###
 
 // @name Import
+// @no-redirect
 POST http://localhost:8088/api/v1/mitm_dataset/import/
 Authorization: Bearer {{jwt_token}}
 X-CSRFToken: {{csrf_token}}
@@ -72,3 +73,4 @@ X-CSRFToken: {{csrf_token}}
 POST http://localhost:8088/api/v1/mitm_dataset/export/
 Authorization: Bearer {{jwt_token}}
 X-CSRFToken: {{csrf_token}}
+
diff --git a/tests/requests/mitm_service_call.http b/tests/requests/mitm_service_call.http
index 8142f383a2..af5cfdab8e 100644
--- a/tests/requests/mitm_service_call.http
+++ b/tests/requests/mitm_service_call.http
@@ -3,7 +3,7 @@
 // @name Call Health
 POST http://localhost:8088/api/v1/ext_service/call/mitm_service/health/
 Authorization: Bearer {{jwt_token}}
-X-Csrftoken: {{csrf_token}}
+X-CSRFToken: {{csrf_token}}
 Content-Type: application/json
 Accept: application/json
 
@@ -15,8 +15,15 @@ Accept: application/json
 ###
 
 // @name Call Result
-GET http://localhost:8088/api/v1/ext_service/result/health/
+GET http://localhost:8088/api/v1/ext_service/result/
 Authorization: Bearer {{jwt_token}}
-X-Csrftoken: {{csrf_token}}
+X-CSRFToken: {{csrf_token}}
+
+###
+
+// @name Async Events
+GET http://localhost:8088/api/v1/async_event/a6da4181-4070-4eaf-b294-0805374a22dc/
+Authorization: Bearer {{jwt_token}}
+X-CSRFToken: {{csrf_token}}
 
 ###
-- 
GitLab